diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b075827 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,267 @@ +# Удалите строку ниже, если вы хотите наследовать параметры .editorconfig из каталогов, расположенных выше в иерархии +root = true + +# Файлы C# +[*.cs] + +#### Основные параметры EditorConfig #### + +# Отступы и интервалы +indent_size = 4 +indent_style = space +tab_width = 4 + +# Предпочтения для новых строк +end_of_line = crlf +insert_final_newline = false + +#### Действия кода .NET #### + +# Члены типа +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Поиск символов +dotnet_search_reference_assemblies = true + +#### Рекомендации по написанию кода .NET #### + +# Упорядочение Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# Предпочтения для this. и Me. +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Параметры использования ключевых слов языка и типов BCL +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Предпочтения для скобок +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Предпочтения модификатора +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Выражения уровень предпочтения +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Предпочтения для полей +dotnet_style_readonly_field = true + +# Настройки параметров +dotnet_code_quality_unused_parameters = all + +# Параметры подавления +dotnet_remove_unnecessary_suppression_exclusions = none + +# Предпочтения для новых строк +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### Рекомендации по написанию кода C# #### + +# Предпочтения var +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Члены, заданные выражениями +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Настройки соответствия шаблонов +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Настройки проверки на null +csharp_style_conditional_delegate_call = true + +# Предпочтения модификатора +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Предпочтения для блоков кода +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = file_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Выражения уровень предпочтения +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# предпочтения для директивы using +csharp_using_directive_placement = outside_namespace + +# Предпочтения для новых строк +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### Правила форматирования C# #### + +# Предпочтения для новых строк +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Параметры отступов +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Предпочтения для интервалов +csharp_space_after_cast = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Параметры переноса +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Стили именования + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +[*.{cs,vb}] +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 \ No newline at end of file diff --git a/Pf2eDataScrapper/EnvSettings.cs b/Pf2eDataScrapper/EnvSettings.cs index 5a5fffb..dd1b457 100644 --- a/Pf2eDataScrapper/EnvSettings.cs +++ b/Pf2eDataScrapper/EnvSettings.cs @@ -1,41 +1,40 @@ using dotenv.net; using dotenv.net.Utilities; -namespace Pf2eDataScrapper +namespace Pf2eDataScrapper; + +/// +/// Класс настроек. +/// +internal static class EnvSettings { /// - /// Класс настроек. + /// Загрузка файла настроек `.env`. /// - internal static class EnvSettings + public static void Load() { - /// - /// Загрузка файла настроек `.env`. - /// - public static void Load() - { - DotEnv.Load(); - } - - /// - /// Путь к папке с входными данными. - /// - public static string DataDirPath => EnvReader.GetStringValue("DATA_DIR"); - - /// - /// Папка в входными данными. - /// - public static DirectoryInfo DataDir => new(DataDirPath); - - /// - /// Путь к папке для сохранения результатов. - /// - public static string OutputDirPath => EnvReader.GetStringValue("OUTPUT_DIR"); - - /// - /// Папка для сохранения результатов. - /// - public static string OutputDir => new(OutputDirPath); - - public static string DatabaseConnection => EnvReader.GetStringValue("DATABASE_CONNECTION"); + DotEnv.Load(); } + + /// + /// Путь к папке с входными данными. + /// + public static string DataDirPath => EnvReader.GetStringValue("DATA_DIR"); + + /// + /// Папка в входными данными. + /// + public static DirectoryInfo DataDir => new(DataDirPath); + + /// + /// Путь к папке для сохранения результатов. + /// + public static string OutputDirPath => EnvReader.GetStringValue("OUTPUT_DIR"); + + /// + /// Папка для сохранения результатов. + /// + public static string OutputDir => new(OutputDirPath); + + public static string DatabaseConnection => EnvReader.GetStringValue("DATABASE_CONNECTION"); } diff --git a/Pf2eDataScrapper/Importers/AbstractImporter.cs b/Pf2eDataScrapper/Importers/AbstractImporter.cs index 1076b91..f1f93ac 100644 --- a/Pf2eDataScrapper/Importers/AbstractImporter.cs +++ b/Pf2eDataScrapper/Importers/AbstractImporter.cs @@ -1,42 +1,41 @@ using Pf2eDataScrapper.Importers.Repositories; -namespace Pf2eDataScrapper.Importers +namespace Pf2eDataScrapper.Importers; + +/// +/// Абстрактный класс импортёра +/// +internal abstract class AbstractImporter { /// - /// Абстрактный класс импортёра + /// Отображаемое имя импортёра /// - internal abstract class AbstractImporter - { - /// - /// Отображаемое имя импортёра - /// - public abstract string Name { get; } + public abstract string Name { get; } - /// - /// Короткое имя импортёра - /// - public abstract string Slug { get; } + /// + /// Короткое имя импортёра + /// + public abstract string Slug { get; } - public abstract AbstractImporterRepository Repository { get; } + public abstract AbstractImporterRepository Repository { get; } - /// - /// Путь к директории с данными для импортёра - /// - public string Dir => Path.Combine(EnvSettings.DataDirPath, Slug); + /// + /// Путь к директории с данными для импортёра + /// + public string Dir => Path.Combine(EnvSettings.DataDirPath, Slug); - /// - /// Метод подготовки данных для импорта - /// - public abstract void Prepare(); + /// + /// Метод подготовки данных для импорта + /// + public abstract void Prepare(); - /// - /// Импорт данных - /// - public abstract void Import(); + /// + /// Импорт данных + /// + public abstract void Import(); - /// - /// Построение схем данных - /// - public abstract void BuildSchemas(); - } + /// + /// Построение схем данных + /// + public abstract void BuildSchemas(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs index ede516a..425274d 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdImporter.cs @@ -1,40 +1,39 @@ -using Pf2eDataScrapper.Importers.Repositories; -using Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; +using Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; +using Pf2eDataScrapper.Importers.Repositories; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; + +internal class AonprdImporter : AbstractImporter { - internal class AonprdImporter : AbstractImporter + public override string Name => "Archives of Nethys Pathfinder 2e"; + + public override string Slug => "aonprd"; + + public override AbstractImporterRepository Repository => new AonprdRepository(this, "https://elasticsearch.aonprd.com"); + + public AonprdLoader Loader { get; } + + public AonprdImporter() { - public override string Name => "Archives of Nethys Pathfinder 2e"; + Loader = new AonprdLoader(this); + } - public override string Slug => "aonprd"; + public override void Prepare() + { + Repository.Update(); + Loader.Prepare(); + } - public override AbstractImporterRepository Repository => new AonprdRepository(this, "https://elasticsearch.aonprd.com"); + public override void BuildSchemas() + { + AonprdSchemaBuilder builder = new(Loader); - public AonprdLoader Loader { get; } + builder.BuildSchemas(); + builder.Save(EnvSettings.OutputDir); + } - public AonprdImporter() - { - Loader = new AonprdLoader(this); - } - - public override void Prepare() - { - Repository.Update(); - Loader.Prepare(); - } - - public override void BuildSchemas() - { - var builder = new AonprdSchemaBuilder(Loader); - - builder.BuildSchemas(); - builder.Save(EnvSettings.OutputDir); - } - - public override void Import() - { - throw new NotImplementedException(); - } + public override void Import() + { + throw new NotImplementedException(); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs index c5baa85..ba0fc32 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdLoader.cs @@ -1,79 +1,77 @@ -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; + +internal class AonprdLoader(AonprdImporter importer) { - internal class AonprdLoader (AonprdImporter importer) + public AonprdImporter Importer = importer; + + public AonprdCompendium Compendium = new(); + + public int ItemCount => Compendium.Count; + + public void Prepare() { - public AonprdImporter Importer = importer; + using StreamReader indexFile = new(Path.Combine(Importer.Dir, "index.json")); + JObject indexJson = JObject.Parse(indexFile.ReadToEnd()); - public AonprdCompendium Compendium = new(); - - public int ItemCount => Compendium.Count; - - public void Prepare() + foreach (KeyValuePair kvp in indexJson) { - using var indexFile = new StreamReader(Path.Combine(Importer.Dir, "index.json")); - var indexJson = JObject.Parse(indexFile.ReadToEnd()); + string file = kvp.Key.ToString(); + JArray items = (JArray) kvp.Value!; - foreach (var kvp in indexJson) + foreach (JToken item in items) { - var file = kvp.Key.ToString(); - var items = (JArray)kvp.Value!; - - foreach (var item in items) - { - Compendium.AddReference(item.ToString(), file); - } + Compendium.AddReference(item.ToString(), file); } } + } - public IEnumerable Entries() - { - return Entries(Compendium.Types.Keys.ToArray()); - } + public IEnumerable Entries() + { + return Entries([.. Compendium.Types.Keys]); + } - public IEnumerable Entries(string types) - { - return Entries([types]); - } + public IEnumerable Entries(string types) + { + return Entries([types]); + } - public IEnumerable Entries(string[] types) + public IEnumerable Entries(string[] types) + { + foreach (string? file in Compendium.Files(types)) { - foreach (var file in Compendium.Files(types)) + if (file is null) { - if (file is null) + continue; + } + + using StreamReader fileReader = new(Path.Combine(Importer.Dir, $"{file}.json")); + JArray array = JArray.Parse(fileReader.ReadToEnd()); + + foreach (JToken item in array) + { + string? id = item.SelectToken("id")?.ToString(); + + if (id is null) { continue; } - using var fileReader = new StreamReader(Path.Combine(Importer.Dir, $"{file}.json")); - var array = JArray.Parse(fileReader.ReadToEnd()); + LoaderEntry entry = new(id, file, (JObject) item); - foreach (var item in array) + if (types.Contains(entry.Type)) { - var id = item.SelectToken("id")?.ToString(); - - if (id is null) - { - continue; - } - - var entry = new LoaderEntry(id, file, (JObject)item); - - if (types.Contains(entry.Type)) - { - yield return entry; - } + yield return entry; } } } + } - public class LoaderEntry(string id, string file, JObject itemJson) : AonprdReference(id, file) - { - public JObject Json { get; } = itemJson; - } + public class LoaderEntry(string id, string file, JObject itemJson) : AonprdReference(id, file) + { + public JObject Json { get; } = itemJson; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs index 1d4cc34..112df9a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/AonprdRepository.cs @@ -2,101 +2,100 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Repositories; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd; + +internal class AonprdRepository(AbstractImporter importer, string searchUrl) : AbstractImporterRepository(importer) { - internal class AonprdRepository(AbstractImporter importer, string searchUrl): AbstractImporterRepository(importer) + public readonly string SearchUrl = searchUrl; + + public int Revision { get; private set; } + + private string Load(string url) { - public readonly string SearchUrl = searchUrl; - - public int Revision { get; private set; } - - private string Load(string url) + if (!url.StartsWith("http://") && !url.StartsWith("https://")) { - if (!url.StartsWith("http://") && !url.StartsWith("https://")) - { - url = SearchUrl + url; - } + url = SearchUrl + url; + } - using var client = new HttpClient(); - using var stream = client.GetStreamAsync(url); - using var reader = new StreamReader(stream.Result); + using HttpClient client = new(); + using Task stream = client.GetStreamAsync(url); + using StreamReader reader = new(stream.Result); - return reader.ReadToEnd(); + return reader.ReadToEnd(); + } + + + public override void Update() + { + if (CheckRevision()) + { + Console.WriteLine($"Данные Aonprd актуальны, текущая ревизия {Revision}."); + return; + } + + Console.WriteLine($"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..."); + + if (Directory.Exists(RepositoryDirPath)) + { + Directory.Delete(RepositoryDirPath, true); + } + Directory.CreateDirectory(RepositoryDirPath); + + WriteFile("revision.txt", Revision.ToString()); + + Console.WriteLine("Загрузка индекса..."); + string indexContent = Load($"/json-data/aon{Revision}-index.json"); + WriteFile("index.json", indexContent); + + JObject indexJson = JObject.Parse(indexContent); + + Console.WriteLine("Загрузка индексных файлов..."); + + ProgressBar bar = new(indexJson.Count); + int i = 0; + + foreach (KeyValuePair kvp in indexJson) + { + string fileName = kvp.Key; + bar.Refresh(i++, fileName); + + string fileContent = Load($"/json-data/{fileName}.json"); + WriteFile($"{fileName}.json", fileContent); } - public override void Update() + bar.Refresh(indexJson.Count, "Готово!"); + } + + /// + /// Проверка актуальности данных + /// + /// true - актуальная ревизия, false - устаревшая/отсутсвует + private bool CheckRevision() + { + JObject page = JObject.Parse(Load("/aon/_doc/feat-1")); + + Revision = Convert.ToInt32(page.SelectToken("_index")?.ToObject()?.Replace("aon", "") ?? throw new Exception("Can't get revision")); + + string revisionFile = Path.Combine(RepositoryDirPath, "revision.txt"); + if (!File.Exists(revisionFile)) { - if (CheckRevision()) - { - Console.WriteLine($"Данные Aonprd актуальны, текущая ревизия {Revision}."); - return; - } - - Console.WriteLine($"Данные Aonprd устарели, обновляем {RepositoryDirPath} до ревизии {Revision}..."); - - if (Directory.Exists(RepositoryDirPath)) - { - Directory.Delete(RepositoryDirPath, true); - } - Directory.CreateDirectory(RepositoryDirPath); - - WriteFile("revision.txt", Revision.ToString()); - - Console.WriteLine("Загрузка индекса..."); - var indexContent = Load($"/json-data/aon{Revision}-index.json"); - WriteFile("index.json", indexContent); - - var indexJson = JObject.Parse(indexContent); - - Console.WriteLine("Загрузка индексных файлов..."); - - var bar = new ProgressBar(indexJson.Count); - var i = 0; - - foreach (var kvp in indexJson) - { - var fileName = kvp.Key; - bar.Refresh(i++, fileName); - - var fileContent = Load($"/json-data/{fileName}.json"); - WriteFile($"{fileName}.json", fileContent); - } - - - bar.Refresh(indexJson.Count, "Готово!"); + return false; } - /// - /// Проверка актуальности данных - /// - /// true - актуальная ревизия, false - устаревшая/отсутсвует - private bool CheckRevision() + using StreamReader reader = new(revisionFile); + string lastRevision = reader.ReadToEnd(); + return lastRevision == Revision.ToString(); + } + + private void WriteFile(string path, string content) + { + string filePath = Path.Combine(RepositoryDirPath, path); + if (File.Exists(filePath)) { - var page = JObject.Parse(Load("/aon/_doc/feat-1")); - - Revision = Convert.ToInt32(page.SelectToken("_index")?.ToObject()?.Replace("aon", "") ?? throw new Exception("Can't get revision")); - - var revisionFile = Path.Combine(RepositoryDirPath, "revision.txt"); - if (!File.Exists(revisionFile)) - { - return false; - } - - using var reader = new StreamReader(revisionFile); - var lastRevision = reader.ReadToEnd(); - return lastRevision == Revision.ToString(); - } - - private void WriteFile (string path, string content) - { - var filePath = Path.Combine(RepositoryDirPath, path); - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - using var writer = new StreamWriter(filePath); - writer.Write(content); + File.Delete(filePath); } + using StreamWriter writer = new(filePath); + writer.Write(content); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs index 761cac2..02b2463 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdCompendium.cs @@ -1,28 +1,27 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; + +internal class AonprdCompendium { - internal class AonprdCompendium + public readonly Dictionary Types = []; + + public void AddReference(string id, string file) { - public readonly Dictionary Types = []; + AonprdReference reference = new(id, file); - public void AddReference(string id, string file) + if (Types.TryGetValue(reference.Type, out AonprdType? type)) { - AonprdReference reference = new(id, file); - - if (Types.TryGetValue(reference.Type, out var type)) - { - type.Add(reference); - } - else - { - Types.Add(reference.Type, new AonprdType(reference)); - } + type.Add(reference); + } + else + { + Types.Add(reference.Type, new AonprdType(reference)); } - - public int Count => Types.Values.Sum(x => x.Count); - - public IEnumerable Files(IEnumerable types) => Types.Values - .Where(t => types.Contains(t.Type)) - .SelectMany(t => t.Files) - .Distinct(); } + + public int Count => Types.Values.Sum(x => x.Count); + + public IEnumerable Files(IEnumerable types) => Types.Values + .Where(t => types.Contains(t.Type)) + .SelectMany(t => t.Files) + .Distinct(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs index 384d066..a54b95b 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdReference.cs @@ -1,35 +1,23 @@  -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; + +internal class AonprdReference(string id, string file) { - internal class AonprdReference(string id, string file) + public readonly string Id = id; + + public readonly string Type = ResolveType(id); + + public readonly string File = file; + + public static string ResolveType(string id) { - public readonly string Id = id; - - public readonly string Type = ResolveType(id); - - public readonly string File = file; - - public static string ResolveType(string id) + static string? subType(string type, string cat) { - static string? subType(string type, string cat) - { - if (type.StartsWith($"{cat}-") && type != cat) - { - if (type.EndsWith("-bonus")) - { - return $"{cat}-bonus"; - } - else - { - return $"{cat}-sub"; - } - } - return null; - } - - var type = string.Join('-', id.Split('-')[0..^1]); - - return subType(type, "equipment") ?? subType(type, "weapon") ?? type; + return type.StartsWith($"{cat}-") && type != cat ? type.EndsWith("-bonus") ? $"{cat}-bonus" : $"{cat}-sub" : null; } + + string type = string.Join('-', id.Split('-')[0..^1]); + + return subType(type, "equipment") ?? subType(type, "weapon") ?? type; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs index 8212a36..5a52fb9 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/Compendium/AonprdType.cs @@ -1,23 +1,22 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; + +internal class AonprdType(string type) { - internal class AonprdType(string type) + public readonly string Type = type; + + public readonly List Items = []; + + public AonprdType(AonprdReference reference) : this(reference.Type) { - public readonly string Type = type; - - public readonly List Items = []; - - public AonprdType(AonprdReference reference) : this(reference.Type) - { - Items.Add(reference); - } - - public void Add(AonprdReference reference) - { - Items.Add(reference); - } - - public List Files => [.. Items.Select(x => x.File).Distinct()]; - - public int Count => Items.Count; + Items.Add(reference); } + + public void Add(AonprdReference reference) + { + Items.Add(reference); + } + + public List Files => [.. Items.Select(x => x.File).Distinct()]; + + public int Count => Items.Count; } diff --git a/Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs index 284132d..f06a888 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Aonprd/SchemaBuilder/AonprdSchemaBuilder.cs @@ -2,86 +2,85 @@ using Pf2eDataScrapper.Importers.Implementations.Aonprd.Compendium; using Pf2eDataScrapper.SchemaBuilder; -namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder +namespace Pf2eDataScrapper.Importers.Implementations.Aonprd.SchemaBuilder; + +internal class AonprdSchemaBuilder(AonprdLoader loader) { - internal class AonprdSchemaBuilder(AonprdLoader loader) + public readonly AonprdLoader Loader = loader; + + public readonly AonprdCompendium Compendium = loader.Compendium; + + // public readonly AonprdSchemaTypeResolver Resolver = new(); + + public readonly Dictionary Schemas = []; + + private SchemaNode GetSchema(AonprdLoader.LoaderEntry entry) { - public readonly AonprdLoader Loader = loader; - - public readonly AonprdCompendium Compendium = loader.Compendium; - - // public readonly AonprdSchemaTypeResolver Resolver = new(); - - public readonly Dictionary Schemas = []; - - private SchemaNode GetSchema(AonprdLoader.LoaderEntry entry) + string key = entry.Type; + if (!Schemas.TryGetValue(key, out SchemaNode? value)) { - string key = entry.Type; - if (!Schemas.TryGetValue(key, out SchemaNode? value)) + value = new SchemaNode() { - value = new SchemaNode() - { - // Resolver = Resolver, - Path = key - }; - Schemas[key] = value; - } - return value; + // Resolver = Resolver, + Path = key + }; + Schemas[key] = value; + } + return value; + } + + public void BuildSchemas() + { + Console.WriteLine("Building schemas..."); + + ProgressBar bar = new ProgressBar(Loader.ItemCount); + int i = 0; + + foreach (AonprdLoader.LoaderEntry entry in Loader.Entries()) + { + bar.Refresh(i++, entry.Type); + GetSchema(entry).AddValue(entry.Json); } - public void BuildSchemas() + bar.Refresh(Loader.ItemCount, "Done!"); + } + + public void Save(string targetDir) + { + Console.WriteLine("Dumping schemas..."); + + targetDir = Path.Combine(targetDir, Loader.Importer.Slug); + + ProgressBar bar = new(Schemas.Count); + int i = 0; + + foreach (KeyValuePair kvp in Schemas) { - Console.WriteLine("Building schemas..."); - - var bar = new ProgressBar(Loader.ItemCount); - var i = 0; - - foreach (var entry in Loader.Entries()) - { - bar.Refresh(i++, entry.Type); - GetSchema(entry).AddValue(entry.Json); - } - - bar.Refresh(Loader.ItemCount, "Done!"); - } - - public void Save(string targetDir) - { - Console.WriteLine("Dumping schemas..."); - - targetDir = Path.Combine(targetDir, Loader.Importer.Slug); - - var bar = new ProgressBar(Schemas.Count); - var i = 0; - - foreach (var kvp in Schemas) - { - bar.Refresh(i++, kvp.Key); - SaveSchema(targetDir, kvp.Key, kvp.Value); - } - } - - protected void SaveSchema(string dir, string name, SchemaNode schema) - { - var invalids = Path.GetInvalidFileNameChars(); - var fileName = string.Join("_", schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.'); - var path = Path.Combine(dir, fileName + ".yaml"); - - var dirPath = Path.GetDirectoryName(path); - if (dirPath is not null && !Directory.Exists(dirPath)) - { - Directory.CreateDirectory(dirPath); - } - - using var writer = new StreamWriter(path); - - schema.Beautify(); - - writer.WriteLine(SchemaNode.FormatYaml(new Dictionary() - { - ["name"] = name, - ["values"] = schema.Values.ToString() - }, schema.Types[0].GetYamlData())); + bar.Refresh(i++, kvp.Key); + SaveSchema(targetDir, kvp.Key, kvp.Value); } } + + protected void SaveSchema(string dir, string name, SchemaNode schema) + { + char[] invalids = Path.GetInvalidFileNameChars(); + string fileName = string.Join("_", schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.'); + string path = Path.Combine(dir, fileName + ".yaml"); + + string? dirPath = Path.GetDirectoryName(path); + if (dirPath is not null && !Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + + using StreamWriter writer = new(path); + + schema.Beautify(); + + writer.WriteLine(SchemaNode.FormatYaml(new Dictionary() + { + ["name"] = name, + ["values"] = schema.Values.ToString() + }, schema.Types[0].GetYamlData())); + } } \ No newline at end of file diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs index ce2b9f5..3c2e48a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumDocumentType.cs @@ -1,18 +1,17 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; + +/// +/// Тип документов компендиума. +/// +internal enum CompendiumDocumentType { - /// - /// Тип документов компендиума. - /// - internal enum CompendiumDocumentType - { - Actor, - Adventure, - Cards, - Item, - JournalEntry, - Macro, - Playlist, - RollTable, - Scene - } + Actor, + Adventure, + Cards, + Item, + JournalEntry, + Macro, + Playlist, + RollTable, + Scene } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs index 41b1b6b..9a6bd91 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolder.cs @@ -1,88 +1,87 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; + +/// +/// Папка компендиума. +/// +internal class CompendiumFolder { /// - /// Папка компендиума. + /// Идентификатор папки. /// - internal class CompendiumFolder + public readonly string Id; + + /// + /// Название папки. + /// + public readonly string Name; + + /// + /// Путь к папке. + /// + public readonly string? Path; + + /// + /// Указатель, что папка содержится в других папках. + /// + public readonly bool IsContained; + + /// + /// Родительская папка. + /// + public CompendiumFolder? Parent; + + /// + /// Тип документов в папке. + /// + public readonly CompendiumDocumentType Type; + + /// + /// Конструктор папки компендиума из JSON-объекта. + /// + /// + public CompendiumFolder(JObject json) { - /// - /// Идентификатор папки. - /// - public readonly string Id; - - /// - /// Название папки. - /// - public readonly string Name; - - /// - /// Путь к папке. - /// - public readonly string? Path; - - /// - /// Указатель, что папка содержится в других папках. - /// - public readonly bool IsContained; - - /// - /// Родительская папка. - /// - public CompendiumFolder? Parent; - - /// - /// Тип документов в папке. - /// - public readonly CompendiumDocumentType Type; - - /// - /// Конструктор папки компендиума из JSON-объекта. - /// - /// - public CompendiumFolder(JObject json) + if (json.TryGetValue("_id", out JToken? id)) { - if (json.TryGetValue("_id", out var id)) - { - // Вложенная папка через _folders.json - Id = id!.ToString(); - Name = json["name"]!.ToString(); - Path = null; - IsContained = true; - } - else - { - // Корневой пак в system.json - Id = json["name"]!.ToString(); - Name = json["label"]!.ToString(); - Path = json["path"]!.ToString(); - IsContained = false; - } - - Type = (CompendiumDocumentType)Enum.Parse(typeof(CompendiumDocumentType), json["type"]!.ToString()); - } - - /// - /// Конструктор папки компендиума. - /// - /// Идентификатор папки. - /// Название папки. - public CompendiumFolder(string id, string name) - { - Id = id; - Name = name; + // Вложенная папка через _folders.json + Id = id!.ToString(); + Name = json["name"]!.ToString(); Path = null; + IsContained = true; + } + else + { + // Корневой пак в system.json + Id = json["name"]!.ToString(); + Name = json["label"]!.ToString(); + Path = json["path"]!.ToString(); IsContained = false; } - /// - /// Строковое представление данных о папки компендиума. - /// - /// Строковое представление данных о папки компендиума. - public override string ToString() - { - return $"CompendiumFolder([Id={Id}][Type={Type}][Name={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])"; - } + Type = (CompendiumDocumentType) Enum.Parse(typeof(CompendiumDocumentType), json["type"]!.ToString()); + } + + /// + /// Конструктор папки компендиума. + /// + /// Идентификатор папки. + /// Название папки. + public CompendiumFolder(string id, string name) + { + Id = id; + Name = name; + Path = null; + IsContained = false; + } + + /// + /// Строковое представление данных о папки компендиума. + /// + /// Строковое представление данных о папки компендиума. + public override string ToString() + { + return $"CompendiumFolder([Id={Id}][Type={Type}][Name={Name}][Path={Path}][Parent={Parent?.Name ?? "null"}])"; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs index 4067ca1..532c5e3 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Compendium/CompendiumFolderCollection.cs @@ -1,28 +1,27 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; + +/// +/// Коллекция папок компендиума +/// +internal class CompendiumFolderCollection : List { /// - /// Коллекция папок компендиума + /// Получение папки по идентификатору /// - internal class CompendiumFolderCollection : List + /// Искомый идентификатор + /// Искомая папка + public CompendiumFolder? GetById(string id) { - /// - /// Получение папки по идентификатору - /// - /// Искомый идентификатор - /// Искомая папка - public CompendiumFolder? GetById(string id) - { - return this.FirstOrDefault(x => x.Id == id); - } + return this.FirstOrDefault(x => x.Id == id); + } - /// - /// Фильтрация требуемых папок компендиума. - /// - /// - public CompendiumFolderCollection Filter() - { - // return [.. this.FindAll(x => x.Type != CompendiumDocumentType.Actor)]; - return [.. this.FindAll(x => x.Type != CompendiumDocumentType.Actor && x.Name == "Conditions")]; - } + /// + /// Фильтрация требуемых папок компендиума. + /// + /// + public CompendiumFolderCollection Filter() + { + // return [.. this.FindAll(x => x.Type != CompendiumDocumentType.Actor)]; + return [.. this.FindAll(x => x.Type != CompendiumDocumentType.Actor && x.Name == "Conditions")]; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs index c039983..1c7d645 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/CompendiumLoader.cs @@ -1,170 +1,169 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; + +/// +/// Загрузчик данных компендиума +/// +/// Импортёр +internal class CompendiumLoader(Pf2EFoundryImporter importer) { /// - /// Загрузчик данных компендиума + /// Импортёр /// - /// Импортёр - internal class CompendiumLoader(Pf2EFoundryImporter importer) + public readonly Pf2EFoundryImporter Importer = importer; + + /// + /// Набор папок компендиума + /// + public CompendiumFolderCollection Collection = []; + + /// + /// Счётчик папок + /// + private int _folderCount = 0; + + /// + /// Общее количество элементов в компендиуме + /// + public int ItemCount { get; protected set; } = 0; + + /// + /// Обработка папок компендиума в поле `packFolders`. + /// + /// + /// + protected void ProcessPackFolders(JArray folders, CompendiumFolder? parent) { - /// - /// Импортёр - /// - public readonly Pf2EFoundryImporter Importer = importer; - - /// - /// Набор папок компендиума - /// - public CompendiumFolderCollection Collection = []; - - /// - /// Счётчик папок - /// - private int _folderCount = 0; - - /// - /// Общее количество элементов в компендиуме - /// - public int ItemCount { get; protected set; } = 0; - - /// - /// Обработка папок компендиума в поле `packFolders`. - /// - /// - /// - protected void ProcessPackFolders(JArray folders, CompendiumFolder? parent) + foreach (JToken token in folders) { - foreach (var token in folders) + JObject obj = (JObject) token; + + string id = $"folder-{_folderCount++}"; + string name = obj["name"]!.ToString(); + + CompendiumFolder folder = new(id, name) { - JObject obj = (JObject)token; + Parent = parent + }; + Collection.Add(folder); - var id = $"folder-{_folderCount++}"; - var name = obj["name"]!.ToString(); - - var folder = new CompendiumFolder(id, name) + if (obj.TryGetValue("packs", out JToken? packs)) + { + foreach (JToken jToken in packs!) { - Parent = parent - }; - Collection.Add(folder); + string packId = jToken.ToString(); + CompendiumFolder? child = Collection.GetById(packId); - if (obj.TryGetValue("packs", out var packs)) - { - foreach (var jToken in packs!) + if (child is not null) { - var packId = jToken.ToString(); - var child = Collection.GetById(packId); - - if (child is not null) - { - child.Parent = folder; - } + child.Parent = folder; } } - - if (obj.TryGetValue("folders", out var innerFolders)) - { - ProcessPackFolders((JArray)innerFolders!, folder); - } } - Collection = Collection.Filter(); + if (obj.TryGetValue("folders", out JToken? innerFolders)) + { + ProcessPackFolders((JArray) innerFolders!, folder); + } } - /// - /// Подготовка набора папок компендиума - /// - public void Prepare() + Collection = Collection.Filter(); + } + + /// + /// Подготовка набора папок компендиума + /// + public void Prepare() + { + using StreamReader systemFile = new(Path.Combine(Importer.Dir, "static", "system.json")); + JObject systemData = JObject.Parse(systemFile.ReadToEnd()); + + foreach (JToken pack in systemData["packs"]!) { - using var systemFile = new StreamReader(Path.Combine(Importer.Dir, "static", "system.json")); - var systemData = JObject.Parse(systemFile.ReadToEnd()); + CompendiumFolder folder = new((JObject) pack); - foreach (var pack in systemData["packs"]!) + Collection.Add(folder); + } + + if (systemData.TryGetValue("packFolders", out JToken? packFolders)) + { + ProcessPackFolders((JArray) packFolders, null); + } + + // Поиск _folders.json + foreach (CompendiumFolder? parent in Collection.ToList()) + { + if (parent.Path is null) { - var folder = new CompendiumFolder((JObject)pack); - - Collection.Add(folder); + continue; } - if (systemData.TryGetValue("packFolders", out var packFolders)) - { - ProcessPackFolders((JArray)packFolders, null); - } + string dirPath = Path.Combine(Importer.Dir, parent.Path); - // Поиск _folders.json - foreach (var parent in Collection.ToList()) + ItemCount += Directory.GetFiles(dirPath, "*.json", SearchOption.AllDirectories) + .Length; + + foreach (string foldersFilePath in Directory.GetFiles(dirPath, "_folders.json", SearchOption.AllDirectories)) { - if (parent.Path is null) + ItemCount--; + using StreamReader file = new(foldersFilePath); + JArray data = JArray.Parse(file.ReadToEnd()); + + foreach (JToken pack in data) + { + CompendiumFolder folder = new((JObject) pack) { Parent = parent }; + Collection.Add(folder); + } + } + } + + // foreach (var folder in Collection) + // { + // Console.WriteLine(folder); + // } + + Collection = Collection.Filter(); + + Console.WriteLine($"Total items: {ItemCount}"); + } + + public IEnumerable Enrties() + { + foreach (CompendiumFolder folder in Collection.FindAll(f => f.Path is not null)) + { + string dirPath = Path.Combine(Importer.Dir, folder.Path!); + + foreach (string filePath in Directory.GetFiles(dirPath, "*.json", SearchOption.AllDirectories)) + { + FileInfo file = new(filePath); + + if (file.Name == "_folders.json") { continue; } - var dirPath = Path.Combine(Importer.Dir, parent.Path); + using StreamReader reader = new(file.FullName); + JObject itemJson = JObject.Parse(reader.ReadToEnd()); - ItemCount += Directory.GetFiles(dirPath, "*.json", SearchOption.AllDirectories) - .Length; - - foreach (var foldersFilePath in Directory.GetFiles(dirPath, "_folders.json", SearchOption.AllDirectories)) + CompendiumFolder? itemFolder = folder; + if (itemJson.TryGetValue("folder", out JToken? itemJsonFolder)) { - ItemCount--; - using var file = new StreamReader(foldersFilePath); - var data = JArray.Parse(file.ReadToEnd()); - - foreach (var pack in data) - { - var folder = new CompendiumFolder((JObject)pack) { Parent = parent }; - Collection.Add(folder); - } + itemFolder = Collection.GetById(itemJsonFolder.ToString()); } + + yield return new LoaderEnrty(file, itemJson, itemFolder ?? folder); } - - // foreach (var folder in Collection) - // { - // Console.WriteLine(folder); - // } - - Collection = Collection.Filter(); - - Console.WriteLine($"Total items: {ItemCount}"); - } - - public IEnumerable Enrties() - { - foreach (var folder in Collection.FindAll(f => f.Path is not null)) - { - var dirPath = Path.Combine(Importer.Dir, folder.Path!); - - foreach (var filePath in Directory.GetFiles(dirPath, "*.json", SearchOption.AllDirectories)) - { - var file = new FileInfo(filePath); - - if (file.Name == "_folders.json") - { - continue; - } - - using var reader = new StreamReader(file.FullName); - var itemJson = JObject.Parse(reader.ReadToEnd()); - - var itemFolder = folder; - if (itemJson.TryGetValue("folder", out var itemJsonFolder)) - { - itemFolder = Collection.GetById(itemJsonFolder.ToString()); - } - - yield return new LoaderEnrty(file, itemJson, itemFolder ?? folder); - } - } - } - - public class LoaderEnrty(FileInfo file, JObject itemJson, CompendiumFolder folder) - { - public FileInfo File { get; } = file; - public JObject Json { get; } = itemJson; - public CompendiumFolder Folder { get; } = folder; - public string Type { get; } = itemJson["type"]?.ToString() - ?? throw new Exception($"Expected to have `type` field in file {file.FullName}"); } } + + public class LoaderEnrty(FileInfo file, JObject itemJson, CompendiumFolder folder) + { + public FileInfo File { get; } = file; + public JObject Json { get; } = itemJson; + public CompendiumFolder Folder { get; } = folder; + public string Type { get; } = itemJson["type"]?.ToString() + ?? throw new Exception($"Expected to have `type` field in file {file.FullName}"); + } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs index f869b5b..a53a4d0 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/AbstractProcessor.cs @@ -1,104 +1,97 @@ using Newtonsoft.Json.Linq; using NpgsqlTypes; using Pf2eDataScrapper.Model; +using Pf2eDataScrapper.Model.Common; using Pf2eDataScrapper.Model.Enum; -using System.ComponentModel; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; + +internal abstract class AbstractProcessor(JObject input) { - internal abstract class AbstractProcessor(JObject input) + public readonly Pf2eTestContext Context = new(); + + public readonly JObject Input = input; + + protected DataBuilderHelper Helper = DataBuilderHelper.Instance; + + public Entity Entity { get; set; } = new(); + + public abstract EntityType Type { get; } + + public abstract IEntity Object { get; } + + public virtual void Process() { - public readonly Pf2eTestContext Context = new(); + Entity.Id = Helper.NextId; + Entity.Type = Type; - public readonly JObject Input = input; + Entity.Name = GetRequired("name"); + Entity.Img = GetRequired("img"); + Entity.Description = GetRequired("system.description.value"); - protected DataBuilderHelper Helper = DataBuilderHelper.Instance; + Entity.IsTranslated = false; + Entity.IsChecked = false; - public Entity Entity { get; set; } = new Entity(); + Entity.Slug = Helper.GenerateSlug(Entity.Name); - public abstract EntityType Type { get; } + Object.Id = Entity.Id; - public abstract IEntity Object { get; } + Context.Entities.Add(Entity); - public virtual void Process() + // TODO Traits, Alias + } + + public void Save() + { + Context.SaveChanges(); + } + + public T GetRequired(string path) + { + JToken token = Input.SelectToken(path) ?? throw new NullReferenceException($"'{path}' is null"); + + return token.ToObject() ?? throw new NullReferenceException($"'{path}' is null"); + } + + public T? GetOptional(string path) + { + JToken? token = Input.SelectToken(path) ?? null; + + return token is null ? default! : token.ToObject() ?? default; + } + + public T EnumRequired(string path) where T : Enum + { + return EnumOptional(path) ?? throw new Exception($"Can't resolve Enum value for {typeof(T).Name} in path {path}"); + } + + public T? EnumOptional(string path) where T : Enum + { + string? value = GetOptional(path); + + if (value == null) { - Entity.Id = Helper.NextId; - Entity.Type = Type; - - Entity.Name = GetRequired("name"); - Entity.Img = GetRequired("img"); - Entity.Description = GetRequired("system.description.value"); - Entity.DescriptionGm = GetOptional("system.description.gm"); - - Entity.IsTranslated = false; - Entity.IsChecked = false; - - Entity.Slug = Helper.GenerateSlug(Entity.Name); - - Object.Id = Entity.Id; - - Context.Entities.Add(Entity); - - // TODO Traits, Alias - } - - public void Save() - { - Context.SaveChanges(); - } - - public T GetRequired(string path) - { - var token = Input.SelectToken(path) ?? throw new NullReferenceException($"'{path}' is null"); - - return token.ToObject() ?? throw new NullReferenceException($"'{path}' is null"); - } - - public T? GetOptional(string path) - { - var token = Input.SelectToken(path) ?? null; - - if (token is null) - { - return default!; - } - - return token.ToObject() ?? default; - } - - public T EnumRequired(string path) where T : Enum - { - return EnumOptional(path) ?? throw new Exception($"Can't resolve Enum value for {typeof(T).Name} in path {path}"); - } - - public T? EnumOptional(string path) where T : Enum - { - string? value = GetOptional(path); - - if (value == null) - { - return default; - } - - foreach (var field in typeof(T).GetFields()) - { - if (Attribute.GetCustomAttribute(field, typeof(PgNameAttribute)) is PgNameAttribute attribute) - { - if (attribute.PgName == value) - { - return (T)field.GetValue(null); - } - } - else - { - if (field.Name == value) - { - return (T)field.GetValue(null); - } - } - } - return default; } + + foreach (System.Reflection.FieldInfo field in typeof(T).GetFields()) + { + if (Attribute.GetCustomAttribute(field, typeof(PgNameAttribute)) is PgNameAttribute attribute) + { + if (attribute.PgName == value) + { + return (T) field.GetValue(null); + } + } + else + { + if (field.Name == value) + { + return (T) field.GetValue(null); + } + } + } + + return default; } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/DataBuilderHelper.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/DataBuilderHelper.cs index f41897e..c92002c 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/DataBuilderHelper.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/DataBuilderHelper.cs @@ -1,28 +1,27 @@ using Slugify; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; + +internal class DataBuilderHelper { - internal class DataBuilderHelper + public Pf2eDatabase Database = Pf2eDatabase.Instance; + + public SlugHelper Slugger = new(); + + private DataBuilderHelper() { - public Pf2eDatabase Database = Pf2eDatabase.Instance; - - public SlugHelper Slugger = new(); - - private DataBuilderHelper() - { - } - - private int _id = 0; - - public int NextId { get => ++_id; } - - public string GenerateSlug(string name) - { - return Slugger.GenerateSlug(name); - } - - - - public readonly static DataBuilderHelper Instance = new(); } + + private int _id = 0; + + public int NextId { get => ++_id; } + + public string GenerateSlug(string name) + { + return Slugger.GenerateSlug(name); + } + + + + public static readonly DataBuilderHelper Instance = new(); } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs index 4306edb..9ad4f57 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/PF2eFoundryDataBuilder.cs @@ -3,84 +3,78 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; using System.Reflection; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; + +internal class PF2eFoundryDataBuilder { - internal class PF2eFoundryDataBuilder + public Pf2eDatabase Database = Pf2eDatabase.Instance; + + /// + /// Загрузчик данных. + /// + public readonly CompendiumLoader Loader; + + /// + /// Коллекция папок компендиума. + /// + public CompendiumFolderCollection Collection => Loader.Collection; + + public Dictionary Processors = []; + + public PF2eFoundryDataBuilder(CompendiumLoader loader) { - public Pf2eDatabase Database = Pf2eDatabase.Instance; + Loader = loader; + PrepareProcessors(); + } - /// - /// Загрузчик данных. - /// - public readonly CompendiumLoader Loader; - - /// - /// Коллекция папок компендиума. - /// - public CompendiumFolderCollection Collection => Loader.Collection; - - public Dictionary Processors = []; - - public PF2eFoundryDataBuilder(CompendiumLoader loader) + public void PrepareProcessors() + { + foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()) { - Loader = loader; - PrepareProcessors(); - } - - public void PrepareProcessors() - { - foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) + if (type.GetCustomAttributes(typeof(Pf2eTypeProcessor), true).Length <= 0) { - if (type.GetCustomAttributes(typeof(Pf2eTypeProcessor), true).Length <= 0) - { - continue; - } - var attribute = type.GetCustomAttribute(); - Processors.Add(attribute!.Type, type.GetConstructor(new[] { typeof(JObject) })!); + continue; } - } - - public AbstractProcessor? GetProcessor(string type, JObject input) - { - if (Processors.TryGetValue(type, out var constructor)) - { - return (AbstractProcessor)constructor.Invoke([input]); - } - - return null; - } - - public void TruncateTables() - { - Console.WriteLine("Truncating all old data..."); - - Database.ExecuteCommand("SELECT f_truncate_tables();"); - - Console.WriteLine("Truncation completed!"); - } - - public void Build() - { - Console.WriteLine("Building data..."); - - var bar = new ProgressBar(Loader.ItemCount); - var i = 0; - - foreach (var entry in Loader.Enrties()) - { - bar.Refresh(i++, $"{entry.Folder.Name}/{entry.File.Name}"); - - var processor = GetProcessor($"{entry.Folder.Type}.{entry.Type}", entry.Json); - if (processor is null) - { - continue; - } - - processor.Process(); - processor.Save(); - } - - bar.Refresh(Loader.ItemCount, "Done!"); + Pf2eTypeProcessor? attribute = type.GetCustomAttribute(); + Processors.Add(attribute!.Type, type.GetConstructor([typeof(JObject)])!); } } + + public AbstractProcessor? GetProcessor(string type, JObject input) + { + return Processors.TryGetValue(type, out ConstructorInfo? constructor) ? (AbstractProcessor) constructor.Invoke([input]) : null; + } + + public void TruncateTables() + { + Console.WriteLine("Truncating all old data..."); + + Database.ExecuteCommand("SELECT f_truncate_tables();"); + + Console.WriteLine("Truncation completed!"); + } + + public void Build() + { + Console.WriteLine("Building data..."); + + ProgressBar bar = new(Loader.ItemCount); + int i = 0; + + foreach (CompendiumLoader.LoaderEnrty entry in Loader.Enrties()) + { + bar.Refresh(i++, $"{entry.Folder.Name}/{entry.File.Name}"); + + AbstractProcessor? processor = GetProcessor($"{entry.Folder.Type}.{entry.Type}", entry.Json); + if (processor is null) + { + continue; + } + + processor.Process(); + processor.Save(); + } + + bar.Refresh(Loader.ItemCount, "Done!"); + } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs index e3609c1..7e5e37a 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Pf2eTypeProcessor.cs @@ -1,10 +1,9 @@ -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder -{ - [AttributeUsage(AttributeTargets.Class)] - internal class Pf2eTypeProcessor(string type, int priority = 0) : Attribute - { - public readonly string Type = type; +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder; - public readonly int Priority = priority; - } +[AttributeUsage(AttributeTargets.Class)] +internal class Pf2eTypeProcessor(string type, int priority = 0) : Attribute +{ + public readonly string Type = type; + + public readonly int Priority = priority; } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs index 9ec2eb4..7964365 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/DataBuilder/Processors/ConditionProcessor.cs @@ -2,32 +2,31 @@ using Pf2eDataScrapper.Model; using Pf2eDataScrapper.Model.Enum; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.DataBuilder.Processors; + +[Pf2eTypeProcessor("Item.condition")] +internal class ConditionProcessor(JObject input) : AbstractProcessor(input) { - [Pf2eTypeProcessor("Item.condition")] - internal class ConditionProcessor(JObject input) : AbstractProcessor(input) + public override EntityType Type => EntityType.Condition; + + public override Condition Object { get; } = new(); + + public override void Process() { - public override EntityType Type => EntityType.Condition; + base.Process(); - public override Condition Object { get; } = new(); - - public override void Process() + JObject extra = new() { - base.Process(); + { "duration", Input.SelectToken("system.duration") }, + { "overrides", Input.SelectToken("system.overrides") }, + { "references", Input.SelectToken("system.references") }, + { "value", Input.SelectToken("system.value") } + }; - JObject extra = new() - { - { "duration", Input.SelectToken("system.duration") }, - { "overrides", Input.SelectToken("system.overrides") }, - { "references", Input.SelectToken("system.references") }, - { "value", Input.SelectToken("system.value") } - }; + Object.Extra = extra.ToString(Newtonsoft.Json.Formatting.Indented); - Object.Extra = extra.ToString(Newtonsoft.Json.Formatting.Indented); + Object.Group = EnumOptional("system.group"); - Object.Group = EnumOptional("system.group"); - - Context.Conditions.Add(Object); - } + Context.Conditions.Add(Object); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs index 7944ad1..9baf816 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/Pf2eFoundryImporter.cs @@ -2,66 +2,65 @@ using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder; using Pf2eDataScrapper.Importers.Repositories; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; + +/// +/// Импортёр для модуля Pf2e FoundryVTT. +/// +internal class Pf2EFoundryImporter : AbstractImporter { /// - /// Импортёр для модуля Pf2e FoundryVTT. + /// Отображаемое имя импортёра. /// - internal class Pf2EFoundryImporter : AbstractImporter + public override string Name => "Pf2e FoundryVTT module"; + + /// + /// Короткое имя импортёра. + /// + public override string Slug => "pf2e"; + + public override AbstractImporterRepository Repository => new GitRepository(this, "https://github.com/foundryvtt/pf2e.git"); + + /// + /// Загрузчик данных компендиума. + /// + public CompendiumLoader Loader { get; } + + /// + /// Конструктор импортёра. + /// + public Pf2EFoundryImporter() { - /// - /// Отображаемое имя импортёра. - /// - public override string Name => "Pf2e FoundryVTT module"; + Loader = new CompendiumLoader(this); + } - /// - /// Короткое имя импортёра. - /// - public override string Slug => "pf2e"; + /// + /// Метод подготовки данных для импорта. + /// + public override void Prepare() + { + Loader.Prepare(); + } - public override AbstractImporterRepository Repository => new GitRepository(this, "https://github.com/foundryvtt/pf2e.git"); + /// + /// Импорт данных. + /// + public override void Import() + { + PF2eFoundryDataBuilder builder = new(Loader); - /// - /// Загрузчик данных компендиума. - /// - public CompendiumLoader Loader { get; } + builder.TruncateTables(); + builder.Build(); + } - /// - /// Конструктор импортёра. - /// - public Pf2EFoundryImporter() - { - Loader = new CompendiumLoader(this); - } + /// + /// Построение схем данных + /// + public override void BuildSchemas() + { + CompendiumSchemaBuilder builder = new(Loader); - /// - /// Метод подготовки данных для импорта. - /// - public override void Prepare() - { - Loader.Prepare(); - } - - /// - /// Импорт данных. - /// - public override void Import() - { - var builder = new PF2eFoundryDataBuilder(Loader); - - builder.TruncateTables(); - builder.Build(); - } - - /// - /// Построение схем данных - /// - public override void BuildSchemas() - { - var builder = new CompendiumSchemaBuilder(Loader); - - builder.BuildSchemas(); - builder.Save(EnvSettings.OutputDir); - } + builder.BuildSchemas(); + builder.Save(EnvSettings.OutputDir); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs index 87befb9..906ba91 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaBuilder.cs @@ -2,191 +2,185 @@ using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.Compendium; using Pf2eDataScrapper.SchemaBuilder; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder; + +/// +/// Класс построения схемы компендиумов. +/// +/// Загрузчик данных. +internal class CompendiumSchemaBuilder(CompendiumLoader loader) { /// - /// Класс построения схемы компендиумов. + /// Загрузчик данных. /// - /// Загрузчик данных. - internal class CompendiumSchemaBuilder(CompendiumLoader loader) + public CompendiumLoader Loader = loader; + + /// + /// Коллекция папок компендиума. + /// + public CompendiumFolderCollection Collection = loader.Collection; + + /// + /// Объект для определения типов полей в компендиумах PF2E. + /// + public CompendiumSchemaTypeResolver Resolver = new(); + + /// + /// Схемы данных по папкам. + /// + public Dictionary PackSchemas = []; + + /// + /// Схемы данных по типам. + /// + public Dictionary TypeSchemas = []; + + private readonly string[] _equipmentTypes = ["equipment", "armor", "backpack", "consumable", "kit", "shield", "treasure", "weapon"]; + + private SchemaNode GetPackSchema(CompendiumFolder folder) { - /// - /// Загрузчик данных. - /// - public CompendiumLoader Loader = loader; - - /// - /// Коллекция папок компендиума. - /// - public CompendiumFolderCollection Collection = loader.Collection; - - /// - /// Объект для определения типов полей в компендиумах PF2E. - /// - public CompendiumSchemaTypeResolver Resolver = new(); - - /// - /// Схемы данных по папкам. - /// - public Dictionary PackSchemas = []; - - /// - /// Схемы данных по типам. - /// - public Dictionary TypeSchemas = []; - - private readonly string[] _equipmentTypes = ["equipment", "armor", "backpack", "consumable", "kit", "shield", "treasure", "weapon"]; - - private SchemaNode GetPackSchema(CompendiumFolder folder) + if (!PackSchemas.TryGetValue(folder, out SchemaNode? value)) { - if (!PackSchemas.TryGetValue(folder, out SchemaNode? value)) + value = new SchemaNode() { - value = new SchemaNode() - { - Resolver = Resolver, - Path = folder.Name - }; - PackSchemas[folder] = value; - } - - return value; + Resolver = Resolver, + Path = folder.Name + }; + PackSchemas[folder] = value; } - private SchemaNode GetTypeSchema(string key) - { - if (!TypeSchemas.TryGetValue(key, out SchemaNode? value)) - { - value = new SchemaNode() - { - Resolver = Resolver, - Path = key - }; - TypeSchemas[key] = value; - } + return value; + } - return value; + private SchemaNode GetTypeSchema(string key) + { + if (!TypeSchemas.TryGetValue(key, out SchemaNode? value)) + { + value = new SchemaNode() + { + Resolver = Resolver, + Path = key + }; + TypeSchemas[key] = value; } - /// - /// Получение набора схем, к которым относится проверяемый объект. - /// - /// Папка компендиума, в которой находится объект. - /// Указанный тип объекта. - /// - protected List GetItemSchemas(CompendiumFolder folder, string? itemType) - { - var key = folder.Type.ToString() + (itemType is not null ? $".{itemType}" : ""); + return value; + } - if (_equipmentTypes.Contains(itemType)) + /// + /// Получение набора схем, к которым относится проверяемый объект. + /// + /// Папка компендиума, в которой находится объект. + /// Указанный тип объекта. + /// + protected List GetItemSchemas(CompendiumFolder folder, string? itemType) + { + string key = folder.Type.ToString() + (itemType is not null ? $".{itemType}" : ""); + + return _equipmentTypes.Contains(itemType) + ? [GetPackSchema(folder), GetTypeSchema(key), GetTypeSchema("Equipment")] + : [GetPackSchema(folder), GetTypeSchema(key)]; + } + + /// + /// Построение схем данных. + /// + public void BuildSchemas() + { + Console.WriteLine("Building schemas..."); + + ProgressBar bar = new(Loader.ItemCount); + int i = 0; + + foreach (CompendiumLoader.LoaderEnrty entry in Loader.Enrties()) + { + bar.Refresh(i++, entry.Folder.Name); + + List schemas = GetItemSchemas(entry.Folder, entry.Type); + + foreach (SchemaNode schema in schemas) { - return [GetPackSchema(folder), GetTypeSchema(key), GetTypeSchema("Equipment")]; - } - else - { - return [GetPackSchema(folder), GetTypeSchema(key)]; + schema.AddValue(entry.Json); } } - /// - /// Построение схем данных. - /// - public void BuildSchemas() + bar.Refresh(Loader.ItemCount, "Done!"); + } + + /// + /// Сохранение данных. + /// + /// Папка для сохранения. + public void Save(string targetDir) + { + Console.WriteLine("Dumping schemas..."); + + ProgressBar bar = new(PackSchemas.Count + TypeSchemas.Count); + int i = 0; + + targetDir = Path.Combine(targetDir, Loader.Importer.Slug); + + if (!Directory.Exists(targetDir)) { - Console.WriteLine("Building schemas..."); + Directory.CreateDirectory(targetDir); + } + Array.ForEach(Directory.GetFiles(@targetDir), File.Delete); - var bar = new ProgressBar(Loader.ItemCount); - var i = 0; + // Packs + string packsDir = Path.Combine(targetDir, "packs"); + Directory.CreateDirectory(packsDir); - foreach (var entry in Loader.Enrties()) - { - bar.Refresh(i++, entry.Folder.Name); - - var schemas = GetItemSchemas(entry.Folder, entry.Type); - - foreach (var schema in schemas) - { - schema.AddValue(entry.Json); - } - } - - bar.Refresh(Loader.ItemCount, "Done!"); + foreach (KeyValuePair pair in PackSchemas) + { + bar.Refresh(i++, pair.Key.Name); + SaveSchema(packsDir, pair.Key.Name, pair.Value); } - /// - /// Сохранение данных. - /// - /// Папка для сохранения. - public void Save(string targetDir) + // Types + string typesDir = Path.Combine(targetDir, "types"); + Directory.CreateDirectory(typesDir); + foreach (KeyValuePair pair in TypeSchemas) { - Console.WriteLine("Dumping schemas..."); - - var bar = new ProgressBar(PackSchemas.Count + TypeSchemas.Count); - var i = 0; - - targetDir = Path.Combine(targetDir, Loader.Importer.Slug); - - if (!Directory.Exists(targetDir)) - { - Directory.CreateDirectory(targetDir); - } - Array.ForEach(Directory.GetFiles(@targetDir), File.Delete); - - // Packs - var packsDir = Path.Combine(targetDir, "packs"); - Directory.CreateDirectory(packsDir); - - foreach (var pair in PackSchemas) - { - bar.Refresh(i++, pair.Key.Name); - SaveSchema(packsDir, pair.Key.Name, pair.Value); - } - - // Types - var typesDir = Path.Combine(targetDir, "types"); - Directory.CreateDirectory(typesDir); - foreach (var pair in TypeSchemas) - { - bar.Refresh(i++, pair.Key); - SaveSchema(typesDir, pair.Key, pair.Value); - } - - Console.WriteLine("Done!"); + bar.Refresh(i++, pair.Key); + SaveSchema(typesDir, pair.Key, pair.Value); } - /// - /// Сохранение конкретной схемы. - /// - /// Корневая папка для сохранения этой категории схем. - /// Название сохраняемой схемы. - /// Сохраняемая схема. - protected void SaveSchema(string dir, string name, SchemaNode schema) + Console.WriteLine("Done!"); + } + + /// + /// Сохранение конкретной схемы. + /// + /// Корневая папка для сохранения этой категории схем. + /// Название сохраняемой схемы. + /// Сохраняемая схема. + protected void SaveSchema(string dir, string name, SchemaNode schema) + { + char[] invalids = Path.GetInvalidFileNameChars(); + string fileName = string.Join("_", schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.'); + + string path = Path.Combine(dir, fileName + ".yaml"); + + string? dirPath = Path.GetDirectoryName(path); + + if (dirPath is not null && !Directory.Exists(dirPath)) { - var invalids = Path.GetInvalidFileNameChars(); - var fileName = string.Join("_", schema.Path.Split(invalids, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.'); - - var path = Path.Combine(dir, fileName + ".yaml"); - - var dirPath = Path.GetDirectoryName(path); - - if (dirPath is not null && !Directory.Exists(dirPath)) - { - Directory.CreateDirectory(dirPath); - } - - using var writer = new StreamWriter(path); - - schema.Beautify(); - - writer.WriteLine(SchemaNode.FormatYaml(new Dictionary() - { - ["name"] = name, - ["values"] = schema.Values.ToString() - }, schema.Types[0].GetYamlData())); - - //writer.WriteLine(name); - //writer.WriteLine($"Different values: {schema.Values}"); - //writer.WriteLine("====================================="); - //writer.Write(schema.ToString()); + Directory.CreateDirectory(dirPath); } + + using StreamWriter writer = new(path); + + schema.Beautify(); + + writer.WriteLine(SchemaNode.FormatYaml(new Dictionary() + { + ["name"] = name, + ["values"] = schema.Values.ToString() + }, schema.Types[0].GetYamlData())); + + //writer.WriteLine(name); + //writer.WriteLine($"Different values: {schema.Values}"); + //writer.WriteLine("====================================="); + //writer.Write(schema.ToString()); } } diff --git a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs index 0808051..370b5eb 100644 --- a/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs +++ b/Pf2eDataScrapper/Importers/Implementations/Pf2eFoundry/SchemaBuilder/CompendiumSchemaTypeResolver.cs @@ -2,61 +2,52 @@ using Pf2eDataScrapper.SchemaBuilder; using Pf2eDataScrapper.SchemaBuilder.Types; -namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder +namespace Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry.SchemaBuilder; + +internal class CompendiumSchemaTypeResolver : TypeResolver { - internal class CompendiumSchemaTypeResolver : TypeResolver + public class FoundryRulesSchemaType(SchemaNode parent) : IgnoreSchemaType(parent) { - public class FoundryRulesSchemaType(SchemaNode parent) : IgnoreSchemaType(parent) + public override string GetTypeName() { - public override string GetTypeName() - { - return "ignored (Foundry rules object)"; - } - } - - protected readonly List DictionaryPaths = [ - "Actor.character.items.[].system.items", - "Actor.character.items.[].system.overlays", - "Item.ancestry.system.items", - "Item.ancestry.system.boosts", - "Item.background.system.boosts", - "Item.background.system.items", - "Item.class.system.items", - "Item.consumable.system.spell.system.damage", - "Item.consumable.system.spell.system.overlays", - "Item.deity.system.spells", - "Item.feat.system.subfeatures.proficiencies", - "Item.kit.system.items", - "Item.kit.system.items.[value].items", - "Item.spell.system.damage", - "Item.spell.system.heightening.damage", - "Item.spell.system.heightening.levels", - "Item.spell.system.heightening.levels.[value].damage", - "Item.spell.system.overlays", - "Item.spell.system.overlays.[value].system.damage", - "Item.spell.system.overlays.[value].system.heightening", - "Item.spell.system.overlays.[value].system.heightening.[value]" - ]; - - /// - /// Частное определение типа поля. - /// - /// Поле схемы данных, для которого создаётся тип. - /// Добавляемое значение. - /// Новый тип для схемы данных. `null`, что бы использовать значение по умолчанию - protected override AbstractSchemaType? CustomResolve(SchemaNode node, JToken? value) - { - if (CheckPath(node, DictionaryPaths)) - { - return new DictionarySchemaType(node); - } - - if (node.Path.EndsWith(".rules")) - { - return new FoundryRulesSchemaType(node); - } - - return null; + return "ignored (Foundry rules object)"; } } + + protected readonly List DictionaryPaths = [ + "Actor.character.items.[].system.items", + "Actor.character.items.[].system.overlays", + "Item.ancestry.system.items", + "Item.ancestry.system.boosts", + "Item.background.system.boosts", + "Item.background.system.items", + "Item.class.system.items", + "Item.consumable.system.spell.system.damage", + "Item.consumable.system.spell.system.overlays", + "Item.deity.system.spells", + "Item.feat.system.subfeatures.proficiencies", + "Item.kit.system.items", + "Item.kit.system.items.[value].items", + "Item.spell.system.damage", + "Item.spell.system.heightening.damage", + "Item.spell.system.heightening.levels", + "Item.spell.system.heightening.levels.[value].damage", + "Item.spell.system.overlays", + "Item.spell.system.overlays.[value].system.damage", + "Item.spell.system.overlays.[value].system.heightening", + "Item.spell.system.overlays.[value].system.heightening.[value]" + ]; + + /// + /// Частное определение типа поля. + /// + /// Поле схемы данных, для которого создаётся тип. + /// Добавляемое значение. + /// Новый тип для схемы данных. `null`, что бы использовать значение по умолчанию + protected override AbstractSchemaType? CustomResolve(SchemaNode node, JToken? value) + { + return CheckPath(node, DictionaryPaths) + ? new DictionarySchemaType(node) + : node.Path.EndsWith(".rules") ? new FoundryRulesSchemaType(node) : (AbstractSchemaType?) null; + } } diff --git a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs index 42b08a3..4f4298f 100644 --- a/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/AbstractImporterRepository.cs @@ -1,13 +1,12 @@ -namespace Pf2eDataScrapper.Importers.Repositories +namespace Pf2eDataScrapper.Importers.Repositories; + +internal abstract class AbstractImporterRepository(AbstractImporter importer) { - internal abstract class AbstractImporterRepository (AbstractImporter importer) - { - public readonly AbstractImporter Importer = importer; + public readonly AbstractImporter Importer = importer; - public string RepositoryDirPath => Importer.Dir; + public string RepositoryDirPath => Importer.Dir; - public DirectoryInfo RepositoryDir => new(RepositoryDirPath); + public DirectoryInfo RepositoryDir => new(RepositoryDirPath); - public abstract void Update(); - } + public abstract void Update(); } diff --git a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs index 45e604a..3f419f9 100644 --- a/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs +++ b/Pf2eDataScrapper/Importers/Repositories/GitRepository.cs @@ -1,52 +1,50 @@ -using System.Data.Common; -using System.Diagnostics; +using System.Diagnostics; -namespace Pf2eDataScrapper.Importers.Repositories +namespace Pf2eDataScrapper.Importers.Repositories; + +/// +/// Загрузчик данных из git-репозиториев +/// +/// +internal class GitRepository(AbstractImporter importer, string url) : AbstractImporterRepository(importer) { + public readonly string Url = url; + /// - /// Загрузчик данных из git-репозиториев + /// Обновление импортёра /// - /// - internal class GitRepository(AbstractImporter importer, string url) : AbstractImporterRepository(importer) + public override void Update() { - public readonly string Url = url; + string gitPath = Path.Combine(RepositoryDirPath, importer.Slug); - /// - /// Обновление импортёра - /// - public override void Update() + if (Directory.Exists(gitPath)) { - var gitPath = Path.Combine(RepositoryDirPath, importer.Slug); - - if (Directory.Exists(gitPath)) + // git pull + Process process = new() { - // git pull - var process = new Process + StartInfo = new ProcessStartInfo() { - StartInfo = new ProcessStartInfo() - { - FileName = "git", - WorkingDirectory = gitPath, - Arguments = "pull", - } - }; - process.Start(); - } - else + FileName = "git", + WorkingDirectory = gitPath, + Arguments = "pull", + } + }; + process.Start(); + } + else + { + // git clone + Process process = new() { - // git clone - var process = new Process + StartInfo = new ProcessStartInfo() { - StartInfo = new ProcessStartInfo() - { - FileName = "git", - WorkingDirectory = RepositoryDirPath, - Arguments = $"clone {Url} {Importer.Slug}", - } - }; - process.Start(); + FileName = "git", + WorkingDirectory = RepositoryDirPath, + Arguments = $"clone {Url} {Importer.Slug}", + } + }; + process.Start(); - } } } } diff --git a/Pf2eDataScrapper/Model/Action.cs b/Pf2eDataScrapper/Model/Action.cs index 06a0fa8..1d4b0ae 100644 --- a/Pf2eDataScrapper/Model/Action.cs +++ b/Pf2eDataScrapper/Model/Action.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/Ancestry.cs b/Pf2eDataScrapper/Model/Ancestry.cs index 3de866b..86ba142 100644 --- a/Pf2eDataScrapper/Model/Ancestry.cs +++ b/Pf2eDataScrapper/Model/Ancestry.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/Background.cs b/Pf2eDataScrapper/Model/Background.cs index c68b325..fdb2897 100644 --- a/Pf2eDataScrapper/Model/Background.cs +++ b/Pf2eDataScrapper/Model/Background.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/CampaignFeature.cs b/Pf2eDataScrapper/Model/CampaignFeature.cs index d0df20b..d0da0fe 100644 --- a/Pf2eDataScrapper/Model/CampaignFeature.cs +++ b/Pf2eDataScrapper/Model/CampaignFeature.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/Class.cs b/Pf2eDataScrapper/Model/Class.cs index 4b21563..7b83a44 100644 --- a/Pf2eDataScrapper/Model/Class.cs +++ b/Pf2eDataScrapper/Model/Class.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/Deity.cs b/Pf2eDataScrapper/Model/Deity.cs index eb15c81..1527f3a 100644 --- a/Pf2eDataScrapper/Model/Deity.cs +++ b/Pf2eDataScrapper/Model/Deity.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/Enum/EntityRarity.cs b/Pf2eDataScrapper/Model/Enum/EntityRarity.cs index 2850e8f..49209e4 100644 --- a/Pf2eDataScrapper/Model/Enum/EntityRarity.cs +++ b/Pf2eDataScrapper/Model/Enum/EntityRarity.cs @@ -1,19 +1,18 @@ using NpgsqlTypes; -namespace Pf2eDataScrapper.Model.Enum +namespace Pf2eDataScrapper.Model.Enum; + +public enum EntityRarity { - public enum EntityRarity - { - [PgName("common")] - Common, + [PgName("common")] + Common, - [PgName("uncommon")] - Uncommon, + [PgName("uncommon")] + Uncommon, - [PgName("rare")] - Rare, + [PgName("rare")] + Rare, - [PgName("unique")] - Unique - } + [PgName("unique")] + Unique } diff --git a/Pf2eDataScrapper/Model/Enum/TraitType.cs b/Pf2eDataScrapper/Model/Enum/TraitType.cs index ceca1e1..2368b79 100644 --- a/Pf2eDataScrapper/Model/Enum/TraitType.cs +++ b/Pf2eDataScrapper/Model/Enum/TraitType.cs @@ -1,13 +1,12 @@ using NpgsqlTypes; -namespace Pf2eDataScrapper.Model.Enum -{ - public enum TraitType - { - [PgName("regular")] - Regular, +namespace Pf2eDataScrapper.Model.Enum; - [PgName("tradition")] - Tradition - } +public enum TraitType +{ + [PgName("regular")] + Regular, + + [PgName("tradition")] + Tradition } diff --git a/Pf2eDataScrapper/Model/Feat.cs b/Pf2eDataScrapper/Model/Feat.cs index 2876981..b020032 100644 --- a/Pf2eDataScrapper/Model/Feat.cs +++ b/Pf2eDataScrapper/Model/Feat.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/Heritage.cs b/Pf2eDataScrapper/Model/Heritage.cs index bacfdff..f2ef764 100644 --- a/Pf2eDataScrapper/Model/Heritage.cs +++ b/Pf2eDataScrapper/Model/Heritage.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Model/IEntity.cs b/Pf2eDataScrapper/Model/IEntity.cs index aac682f..27f2018 100644 --- a/Pf2eDataScrapper/Model/IEntity.cs +++ b/Pf2eDataScrapper/Model/IEntity.cs @@ -1,12 +1,11 @@ using Pf2eDataScrapper.Model.Common; -namespace Pf2eDataScrapper.Model +namespace Pf2eDataScrapper.Model; + +internal interface IEntity { - internal interface IEntity - { - public int Id { get; set; } + public int Id { get; set; } - public Entity Entity { get; set; } + public Entity Entity { get; set; } - } } diff --git a/Pf2eDataScrapper/Model/Spell.cs b/Pf2eDataScrapper/Model/Spell.cs index 9711dad..93fb1b2 100644 --- a/Pf2eDataScrapper/Model/Spell.cs +++ b/Pf2eDataScrapper/Model/Spell.cs @@ -1,5 +1,4 @@ using Pf2eDataScrapper.Model.Common; -using Pf2eDataScrapper.Model.Enum; namespace Pf2eDataScrapper.Model; diff --git a/Pf2eDataScrapper/Pf2eDatabase.cs b/Pf2eDataScrapper/Pf2eDatabase.cs index b2187a2..cc3ddad 100644 --- a/Pf2eDataScrapper/Pf2eDatabase.cs +++ b/Pf2eDataScrapper/Pf2eDatabase.cs @@ -1,123 +1,122 @@ using Npgsql; using System.Text.RegularExpressions; -namespace Pf2eDataScrapper +namespace Pf2eDataScrapper; + +public partial class Pf2eDatabase { - public partial class Pf2eDatabase + public NpgsqlConnection Connection; + + public readonly string Username; + public readonly string DatabaseName; + + + private Pf2eDatabase() { - public NpgsqlConnection Connection; + Username = UsernamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; + DatabaseName = DatabaseNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - public readonly string Username; - public readonly string DatabaseName; - - - private Pf2eDatabase() - { - Username = UsernamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - DatabaseName = DatabaseNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - - Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection); - Connection.Open(); - } - - ~Pf2eDatabase() - { - Connection.Close(); - } - - public static Pf2eDatabase Instance { get; } = new(); - - public NpgsqlTransaction? Transaction { get; private set; } - - public void StartTransaction() - { - if (Transaction != null) - { - throw new InvalidOperationException("Transaction already started."); - } - - Transaction = Connection.BeginTransaction(); - } - - public void Commit() - { - if (Transaction == null) - { - throw new InvalidOperationException("No transaction started."); - } - - Transaction.Commit(); - } - - public void ExecuteCommand(string sql) - { - var command = GetCommand(sql); - - command.ExecuteNonQuery(); - } - - public NpgsqlCommand GetCommand(string sql) - { - return new NpgsqlCommand(sql, Connection); - } - - - - - /* - public void RebuildDatabase() - { - // Закрываем текущее соединение - Connection.Close(); - - // Получение имён - var databaseNamePattern = DatabaseNamePattern(); - var userConnection = databaseNamePattern.Replace(EnvSettings.DatabaseConnection, ";"); - var databaseName = databaseNamePattern.Match(EnvSettings.DatabaseConnection).Groups[1].Value; - var username = UserNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; - - // Временное подключение к базе данных - NpgsqlConnection tmpConnection = new(userConnection); - tmpConnection.Open(); - - // Удаляем базу данных - NpgsqlCommand dropCommand = new($"DROP DATABASE \"{databaseName}\";", tmpConnection); - dropCommand.ExecuteNonQuery(); - - // Создаём новую базу данных - NpgsqlCommand createCommand = new($@" - CREATE DATABASE {databaseName} - WITH OWNER = {username} - ENCODING = 'UTF8' - CONNECTION LIMIT = -1; - ", tmpConnection); - createCommand.ExecuteNonQuery(); - - string[] permissionCommands = [ - $"GRANT TEMPORARY, CONNECT ON DATABASE {databaseName} TO PUBLIC;", - $"GRANT ALL ON DATABASE {databaseName} TO {username} WITH GRANT OPTION;", - $"GRANT ALL ON DATABASE {databaseName} TO postgres;" - ]; - - foreach (var command in permissionCommands) - { - NpgsqlCommand permissionCommand = new(command, tmpConnection); - permissionCommand.ExecuteNonQuery(); - } - - // Закрываем временное соединение - tmpConnection.Close(); - - // Заново создаём соединение - Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection); - } - - */ - - [GeneratedRegex(@"(?:^|;)Database=(.*?)(?:$|;)")] - private static partial Regex DatabaseNamePattern(); - - [GeneratedRegex(@"(?:^|;)User=(.*?)(?:$|;)")] - private static partial Regex UsernamePattern(); + Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection); + Connection.Open(); } + + ~Pf2eDatabase() + { + Connection.Close(); + } + + public static Pf2eDatabase Instance { get; } = new(); + + public NpgsqlTransaction? Transaction { get; private set; } + + public void StartTransaction() + { + if (Transaction != null) + { + throw new InvalidOperationException("Transaction already started."); + } + + Transaction = Connection.BeginTransaction(); + } + + public void Commit() + { + if (Transaction == null) + { + throw new InvalidOperationException("No transaction started."); + } + + Transaction.Commit(); + } + + public void ExecuteCommand(string sql) + { + NpgsqlCommand command = GetCommand(sql); + + command.ExecuteNonQuery(); + } + + public NpgsqlCommand GetCommand(string sql) + { + return new NpgsqlCommand(sql, Connection); + } + + + + + /* + public void RebuildDatabase() + { + // Закрываем текущее соединение + Connection.Close(); + + // Получение имён + var databaseNamePattern = DatabaseNamePattern(); + var userConnection = databaseNamePattern.Replace(EnvSettings.DatabaseConnection, ";"); + var databaseName = databaseNamePattern.Match(EnvSettings.DatabaseConnection).Groups[1].Value; + var username = UserNamePattern().Match(EnvSettings.DatabaseConnection).Groups[1].Value; + + // Временное подключение к базе данных + NpgsqlConnection tmpConnection = new(userConnection); + tmpConnection.Open(); + + // Удаляем базу данных + NpgsqlCommand dropCommand = new($"DROP DATABASE \"{databaseName}\";", tmpConnection); + dropCommand.ExecuteNonQuery(); + + // Создаём новую базу данных + NpgsqlCommand createCommand = new($@" + CREATE DATABASE {databaseName} + WITH OWNER = {username} + ENCODING = 'UTF8' + CONNECTION LIMIT = -1; + ", tmpConnection); + createCommand.ExecuteNonQuery(); + + string[] permissionCommands = [ + $"GRANT TEMPORARY, CONNECT ON DATABASE {databaseName} TO PUBLIC;", + $"GRANT ALL ON DATABASE {databaseName} TO {username} WITH GRANT OPTION;", + $"GRANT ALL ON DATABASE {databaseName} TO postgres;" + ]; + + foreach (var command in permissionCommands) + { + NpgsqlCommand permissionCommand = new(command, tmpConnection); + permissionCommand.ExecuteNonQuery(); + } + + // Закрываем временное соединение + tmpConnection.Close(); + + // Заново создаём соединение + Connection = new NpgsqlConnection(EnvSettings.DatabaseConnection); + } + + */ + + [GeneratedRegex(@"(?:^|;)Database=(.*?)(?:$|;)")] + private static partial Regex DatabaseNamePattern(); + + [GeneratedRegex(@"(?:^|;)User=(.*?)(?:$|;)")] + private static partial Regex UsernamePattern(); } diff --git a/Pf2eDataScrapper/Pf2eTestContext.cs b/Pf2eDataScrapper/Pf2eTestContext.cs index 7f34c6c..b66930e 100644 --- a/Pf2eDataScrapper/Pf2eTestContext.cs +++ b/Pf2eDataScrapper/Pf2eTestContext.cs @@ -1,9 +1,7 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Pf2eDataScrapper.Model; +using Pf2eDataScrapper.Model.Common; using Pf2eDataScrapper.Model.Enum; -using System.ComponentModel; -using System.Reflection; namespace Pf2eDataScrapper; @@ -103,8 +101,10 @@ public partial class Pf2eTestContext : DbContext public virtual DbSet Traits { get; set; } #endregion + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { + EnvSettings.Load(); optionsBuilder.UseNpgsql(EnvSettings.DatabaseConnection, builder => { @@ -115,50 +115,6 @@ public partial class Pf2eTestContext : DbContext ); } - protected string FromEnum(T enumValue) where T : Enum - { - var enumFieldName = enumValue.ToString(); - var field = typeof(T).GetField(enumFieldName); - if (field is null) - { - return enumFieldName; - } - - var attribute = field.GetCustomAttribute(); - return attribute is null ? enumFieldName : attribute.Description; - } - - protected T ToEnum(string enumValue) where T : Enum - { - var enumType = typeof(T); - foreach (var field in enumType.GetFields()) - { - var attribute = field.GetCustomAttribute(); - if (attribute is null) - { - if (field.Name == enumValue) - { - return (T)field.GetValue(null)!; - } - } - else - { - if (attribute.Description == enumValue) - { - return (T)field.GetValue(null)!; - } - } - } - throw new ArgumentException($"No matching value found for {enumValue} in {enumType.Name}"); - } - - protected ValueConverter GetEnumConverter() where T : Enum - { - return new ValueConverter( - v => FromEnum(v), - v => ToEnum(v)); - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.UseIdentityByDefaultColumns(); @@ -198,8 +154,7 @@ public partial class Pf2eTestContext : DbContext entity.Property(e => e.Id).HasColumnName("id"); entity.Property(e => e.Description).HasColumnName("description"); entity.Property(e => e.DescriptionGm).HasColumnName("description_gm"); - entity.Property(e => e.Type) - .HasColumnName("type"); + entity.Property(e => e.Type).HasColumnName("type"); entity.Property(e => e.Img) .HasMaxLength(150) .HasColumnName("img"); diff --git a/Pf2eDataScrapper/Program.cs b/Pf2eDataScrapper/Program.cs index 181a6d0..57a9bb7 100644 --- a/Pf2eDataScrapper/Program.cs +++ b/Pf2eDataScrapper/Program.cs @@ -1,10 +1,9 @@ using Pf2eDataScrapper; using Pf2eDataScrapper.Importers.Implementations.Aonprd; -using Pf2eDataScrapper.Importers.Implementations.Pf2eFoundry; EnvSettings.Load(); -var importer = new AonprdImporter(); +AonprdImporter importer = new AonprdImporter(); importer.Prepare(); importer.BuildSchemas(); diff --git a/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs b/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs index 72fce6b..098f985 100644 --- a/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs +++ b/Pf2eDataScrapper/SchemaBuilder/SchemaNode.cs @@ -1,244 +1,243 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.SchemaBuilder.Types; -namespace Pf2eDataScrapper.SchemaBuilder +namespace Pf2eDataScrapper.SchemaBuilder; + +/// +/// Элемент схемы данных. +/// +internal class SchemaNode { /// - /// Элемент схемы данных. + /// Отображаемое имя элемента. /// - internal class SchemaNode + public string Name = ""; + + /// + /// Путь к элементу. + /// + public string Path = ""; + + /// + /// Родительский элемент. + /// + public SchemaNode? Parent = null; + + /// + /// Указание, является ли элемент корневым. + /// + public bool Root = false; + + /// + /// Корневой элемент схемы данных. + /// + public SchemaNode RootNode => Root ? this : Parent!.RootNode; + + /// + /// Типы данных, использующиеся в элементе. + /// + public List Types = []; + + /// + /// Указание, может ли этот элемент отсутсвовать в структуре данных. + /// + public bool IsNullable = false; + + /// + /// Объект для определения типа поля в схеме данных. + /// + public TypeResolver Resolver = new(); + + /// + /// Количество значений, добавленных в тип. + /// + public int Values { get; protected set; } = 0; + + /// + /// Указание, является ли тип элемента простым. + /// + public bool IsSimple => Types.Count == 1; + + /// + /// Конструктор корневого элемента. + /// + public SchemaNode() { - /// - /// Отображаемое имя элемента. - /// - public string Name = ""; + this.Root = true; + } - /// - /// Путь к элементу. - /// - public string Path = ""; + /// + /// Конструктор элемента схемы данных. + /// + /// Родительский элемент. + /// Имя элемента. + public SchemaNode(SchemaNode parent, string name) + { + this.Parent = parent; + this.Name = name; + this.Path = parent.Path + "." + name; + this.Resolver = parent.Resolver; + } - /// - /// Родительский элемент. - /// - public SchemaNode? Parent = null; - - /// - /// Указание, является ли элемент корневым. - /// - public bool Root = false; - - /// - /// Корневой элемент схемы данных. - /// - public SchemaNode RootNode => Root ? this : Parent!.RootNode; - - /// - /// Типы данных, использующиеся в элементе. - /// - public List Types = []; - - /// - /// Указание, может ли этот элемент отсутсвовать в структуре данных. - /// - public bool IsNullable = false; - - /// - /// Объект для определения типа поля в схеме данных. - /// - public TypeResolver Resolver = new(); - - /// - /// Количество значений, добавленных в тип. - /// - public int Values { get; protected set; } = 0; - - /// - /// Указание, является ли тип элемента простым. - /// - public bool IsSimple => Types.Count == 1; - - /// - /// Конструктор корневого элемента. - /// - public SchemaNode() + /// + /// Добавление нового возможного значения. + /// + /// Добавляемое значение + public void AddValue(JToken? value) + { + if (value is null || Resolver.TokenType(value) == JTokenType.Null) { - this.Root = true; + IsNullable = true; + return; } - /// - /// Конструктор элемента схемы данных. - /// - /// Родительский элемент. - /// Имя элемента. - public SchemaNode(SchemaNode parent, string name) - { - this.Parent = parent; - this.Name = name; - this.Path = parent.Path + "." + name; - this.Resolver = parent.Resolver; - } + Values++; - /// - /// Добавление нового возможного значения. - /// - /// Добавляемое значение - public void AddValue(JToken? value) + foreach (AbstractSchemaType type in Types) { - if (value is null || Resolver.TokenType(value) == JTokenType.Null) + if (type.IsCompatibleValue(value)) { - IsNullable = true; + type.AddValue(value); return; } - - Values++; - - foreach (var type in Types) - { - if (type.IsCompatibleValue(value)) - { - type.AddValue(value); - return; - } - } - - var newType = Resolver.Resolve(this, value); - - if (newType.IsCompatibleValue(value)) - { - Types.Add(newType); - newType.AddValue(value); - } - else - { - var fallbackType = Resolver.Resolve(this, value, true); - Types.Add(fallbackType); - fallbackType.AddValue(value); - } } - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public string GetTypeName() + AbstractSchemaType newType = Resolver.Resolve(this, value); + + if (newType.IsCompatibleValue(value)) { - if (IsSimple) - { - return Types[0].GetTypeName(); - } - - var typeNames = Types.Select(t => t.GetTypeName()).ToList(); - - return string.Join(" | ", typeNames); + Types.Add(newType); + newType.AddValue(value); } - - /// - /// Полное строковое представление элемента. - /// - /// Строковое представление элемента. - public override string ToString() + else { - var result = new Dictionary() - { - ["type"] = GetTypeName(), - ["path"] = Path, - ["is_null"] = IsNullable ? "true" : "false", - ["values"] = Values.ToString() - }; - - if (IsSimple) - { - return FormatYaml(result, Types[0].GetYamlData()); - } - - if (Types.Count == 0) - { - return FormatYaml(result, new() { ["is_empty"] = "true" }); - } - - List types = []; - - foreach (var type in Types) - { - types.Add(Indent(type?.ToString() ?? "")); - } - - return FormatYaml(result, new() { ["types"] = "- " + string.Join("\r\n- ", types) }); - } - - /// - /// Подготовка элемента к красивому представлению. - /// - public void Beautify() - { - foreach (var type in Types) - { - type.Beautify(); - } - } - - - - /// - /// Отступ для строкового представления. - /// - public const string Tab = " "; - - /// - /// Предел количество отображаемых элементов. - /// - public const int ElementLimit = 20; - - /// - /// Увеличение отступа строки. - /// - /// Изменяемая строка. - /// Строка с добавленными отступами. - public static string Indent(string text) - { - return text.Replace("\r\n", "\r\n" + Tab); - } - - /// - /// Форматирование данных в YAML. - /// - /// Словарь данных. - /// Строка форматированных данных - public static string FormatYaml(params List> input) - { - var result = ""; - - foreach (var kvp in MergeDictionaries(input)) - { - var val = kvp.Value.Contains("\r\n") ? $"\r\n{Tab}{Indent(kvp.Value)}" : kvp.Value; - - result += $"{kvp.Key}: {val}\r\n"; - } - - return result.Trim(); - } - - public static Dictionary MergeDictionaries(params List> input) - { - var result = new Dictionary(); - - foreach (var item in input) - { - foreach (var kvp in item) - { - if (result.ContainsKey(kvp.Key)) - { - result[kvp.Key] = kvp.Value; - } - else - { - result.Add(kvp.Key, kvp.Value); - } - } - } - - return result; + AbstractSchemaType fallbackType = Resolver.Resolve(this, value, true); + Types.Add(fallbackType); + fallbackType.AddValue(value); } } + + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public string GetTypeName() + { + if (IsSimple) + { + return Types[0].GetTypeName(); + } + + List typeNames = [.. Types.Select(t => t.GetTypeName())]; + + return string.Join(" | ", typeNames); + } + + /// + /// Полное строковое представление элемента. + /// + /// Строковое представление элемента. + public override string ToString() + { + Dictionary result = new() + { + ["type"] = GetTypeName(), + ["path"] = Path, + ["is_null"] = IsNullable ? "true" : "false", + ["values"] = Values.ToString() + }; + + if (IsSimple) + { + return FormatYaml(result, Types[0].GetYamlData()); + } + + if (Types.Count == 0) + { + return FormatYaml(result, new() { ["is_empty"] = "true" }); + } + + List types = []; + + foreach (AbstractSchemaType type in Types) + { + types.Add(Indent(type?.ToString() ?? "")); + } + + return FormatYaml(result, new() { ["types"] = "- " + string.Join("\r\n- ", types) }); + } + + /// + /// Подготовка элемента к красивому представлению. + /// + public void Beautify() + { + foreach (AbstractSchemaType type in Types) + { + type.Beautify(); + } + } + + + + /// + /// Отступ для строкового представления. + /// + public const string Tab = " "; + + /// + /// Предел количество отображаемых элементов. + /// + public const int ElementLimit = 20; + + /// + /// Увеличение отступа строки. + /// + /// Изменяемая строка. + /// Строка с добавленными отступами. + public static string Indent(string text) + { + return text.Replace("\r\n", "\r\n" + Tab); + } + + /// + /// Форматирование данных в YAML. + /// + /// Словарь данных. + /// Строка форматированных данных + public static string FormatYaml(params List> input) + { + string result = ""; + + foreach (KeyValuePair kvp in MergeDictionaries(input)) + { + string val = kvp.Value.Contains("\r\n") ? $"\r\n{Tab}{Indent(kvp.Value)}" : kvp.Value; + + result += $"{kvp.Key}: {val}\r\n"; + } + + return result.Trim(); + } + + public static Dictionary MergeDictionaries(params List> input) + { + Dictionary result = []; + + foreach (Dictionary item in input) + { + foreach (KeyValuePair kvp in item) + { + if (result.ContainsKey(kvp.Key)) + { + result[kvp.Key] = kvp.Value; + } + else + { + result.Add(kvp.Key, kvp.Value); + } + } + } + + return result; + } } diff --git a/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs b/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs index 4ed3eb5..7b3b1e9 100644 --- a/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs +++ b/Pf2eDataScrapper/SchemaBuilder/TypeResolver.cs @@ -1,125 +1,111 @@ using Newtonsoft.Json.Linq; using Pf2eDataScrapper.SchemaBuilder.Types; -namespace Pf2eDataScrapper.SchemaBuilder +namespace Pf2eDataScrapper.SchemaBuilder; + +/// +/// Класс для определения типа поля в схеме данных. +/// +internal class TypeResolver { /// - /// Класс для определения типа поля в схеме данных. + /// Проверка соответствия пути. /// - internal class TypeResolver + /// Элемент, путь которого проверяется. + /// Проверяемый путь. Допускается использование `*` в местах, где допускается любое значение. + /// `true`, если `node.Path` соответсвует указанному в `path` значению. + public bool CheckPath(SchemaNode node, string path) { - /// - /// Проверка соответствия пути. - /// - /// Элемент, путь которого проверяется. - /// Проверяемый путь. Допускается использование `*` в местах, где допускается любое значение. - /// `true`, если `node.Path` соответсвует указанному в `path` значению. - public bool CheckPath(SchemaNode node, string path) + if (!path.Contains("*")) { - if (!path.Contains("*")) - { - return path == node.Path; - } - - var pathParts = path.Split('.'); - var nodeParts = node.Path.Split('.'); - - if (pathParts.Length > nodeParts.Length) - { - return false; - } - - return !pathParts.Where((t, i) => t != "*" && t != nodeParts[i]).Any(); + return path == node.Path; } - /// - /// Проверка соответствия пути. - /// - /// Элемент, путь которого проверяется. - /// Проверяемые пути. Допускается использование `*` в местах, где допускается любое значение. - /// `true`, если `node.Path` соответсвует хотя бы одному указанному в `paths` значению. - public bool CheckPath(SchemaNode node, ICollection paths) + string[] pathParts = path.Split('.'); + string[] nodeParts = node.Path.Split('.'); + + return pathParts.Length > nodeParts.Length ? false : !pathParts.Where((t, i) => t != "*" && t != nodeParts[i]).Any(); + } + + /// + /// Проверка соответствия пути. + /// + /// Элемент, путь которого проверяется. + /// Проверяемые пути. Допускается использование `*` в местах, где допускается любое значение. + /// `true`, если `node.Path` соответсвует хотя бы одному указанному в `paths` значению. + public bool CheckPath(SchemaNode node, ICollection paths) + { + return paths.Any(path => CheckPath(node, path)); + } + + /// + /// Получение типа значения. + /// + /// Значение. + /// Типа значения. + public JTokenType TokenType(JToken? value) + { + return value is null || value.Type is JTokenType.Null or JTokenType.Undefined or JTokenType.None ? JTokenType.Null : value.Type; + } + + /// + /// Частное определение типа поля. + /// + /// Поле схемы данных, для которого создаётся тип. + /// Добавляемое значение. + /// Новый тип для схемы данных. `null`, что бы использовать значение по умолчанию + protected virtual AbstractSchemaType? CustomResolve(SchemaNode node, JToken? value) + { + return null; + } + + /// + /// Определение типа поля. + /// + /// Поле схемы данных, для которого создаётся тип. + /// Добавляемое значение. + /// Добавляемое значение. + /// Новый тип для схемы данных + /// + /// Возникает, если у `value` значение идентично `null` + /// (такие значения должны обрабаотываться в `SchemaNode`). + /// + public AbstractSchemaType Resolve(SchemaNode node, JToken? value, bool disableResolve) + { + if (!disableResolve) { - return paths.Any(path => CheckPath(node, path)); + AbstractSchemaType? customType = this.CustomResolve(node, value); + + if (customType is not null) + { + return customType; + } } - /// - /// Получение типа значения. - /// - /// Значение. - /// Типа значения. - public JTokenType TokenType(JToken? value) - { - if (value is null || value.Type is JTokenType.Null or JTokenType.Undefined or JTokenType.None) - { - return JTokenType.Null; - } + JTokenType type = TokenType(value); - return value.Type; - } - - /// - /// Частное определение типа поля. - /// - /// Поле схемы данных, для которого создаётся тип. - /// Добавляемое значение. - /// Новый тип для схемы данных. `null`, что бы использовать значение по умолчанию - protected virtual AbstractSchemaType? CustomResolve(SchemaNode node, JToken? value) - { - return null; - } - - /// - /// Определение типа поля. - /// - /// Поле схемы данных, для которого создаётся тип. - /// Добавляемое значение. - /// Добавляемое значение. - /// Новый тип для схемы данных - /// - /// Возникает, если у `value` значение идентично `null` - /// (такие значения должны обрабаотываться в `SchemaNode`). - /// - public AbstractSchemaType Resolve(SchemaNode node, JToken? value, bool disableResolve) - { - if (!disableResolve) - { - var customType = this.CustomResolve(node, value); - - if (customType is not null) - { - return customType; - } - } - - var type = TokenType(value); - - if (type == JTokenType.Null) - { - throw new ArgumentNullException(nameof(value)); - } - - return type switch + return type == JTokenType.Null + ? throw new ArgumentNullException(nameof(value)) + : type switch { JTokenType.Array => new ArraySchemaType(node), JTokenType.Object => new ObjectSchemaType(node), _ => new StaticSchemaType(node, type) }; - } + } - /// - /// Определение типа поля. - /// - /// Поле схемы данных, для которого создаётся тип. - /// Добавляемое значение. - /// Новый тип для схемы данных - /// - /// Возникает, если у `value` значение идентично `null` - /// (такие значения должны обрабаотываться в `SchemaNode`). - /// - public AbstractSchemaType Resolve(SchemaNode node, JToken? value) - { - return this.Resolve(node, value, false); - } + /// + /// Определение типа поля. + /// + /// Поле схемы данных, для которого создаётся тип. + /// Добавляемое значение. + /// Новый тип для схемы данных + /// + /// Возникает, если у `value` значение идентично `null` + /// (такие значения должны обрабаотываться в `SchemaNode`). + /// + public AbstractSchemaType Resolve(SchemaNode node, JToken? value) + { + return this.Resolve(node, value, false); } } diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs index 425ca39..670c63f 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/AbstractSchemaType.cs @@ -1,65 +1,64 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.SchemaBuilder.Types +namespace Pf2eDataScrapper.SchemaBuilder.Types; + +/// +/// Абстрактный тип представоления данных в схеме. +/// +/// Элемент, к которому добавляется тип. +internal abstract class AbstractSchemaType(SchemaNode parent) { /// - /// Абстрактный тип представоления данных в схеме. + /// Элемент схемы данных, к которому относится тип. /// - /// Элемент, к которому добавляется тип. - internal abstract class AbstractSchemaType(SchemaNode parent) + public SchemaNode Parent = parent; + + /// + /// Корневой элемент схемы данных. + /// + public SchemaNode Root => Parent.RootNode; + + /// + /// Объект для определения типа поля в схеме данных. + /// + public TypeResolver Resolver => Root.Resolver; + + /// + /// Проверка, что значение совместимо с текущим типом. + /// + /// Проверяемое значение. + /// `true`, если значение совместимо с текущим типом. + public abstract bool IsCompatibleValue(JToken value); + + /// + /// Добавление нового значения. + /// + /// Новое значение. + public abstract void AddValue(JToken value); + + /// + /// Подготовка типа к красивому представлению. + /// + public abstract void Beautify(); + + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public abstract string GetTypeName(); + + /// + /// Получение данных для форматирования в YAML. + /// + /// Словарь данных + public abstract Dictionary GetYamlData(); + + /// + /// Полное строковое представление элемента. + /// + /// Строковое представление элемента. + public override string ToString() { - /// - /// Элемент схемы данных, к которому относится тип. - /// - public SchemaNode Parent = parent; - - /// - /// Корневой элемент схемы данных. - /// - public SchemaNode Root => Parent.RootNode; - - /// - /// Объект для определения типа поля в схеме данных. - /// - public TypeResolver Resolver => Root.Resolver; - - /// - /// Проверка, что значение совместимо с текущим типом. - /// - /// Проверяемое значение. - /// `true`, если значение совместимо с текущим типом. - public abstract bool IsCompatibleValue(JToken value); - - /// - /// Добавление нового значения. - /// - /// Новое значение. - public abstract void AddValue(JToken value); - - /// - /// Подготовка типа к красивому представлению. - /// - public abstract void Beautify(); - - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public abstract string GetTypeName(); - - /// - /// Получение данных для форматирования в YAML. - /// - /// Словарь данных - public abstract Dictionary GetYamlData(); - - /// - /// Полное строковое представление элемента. - /// - /// Строковое представление элемента. - public override string ToString() - { - return SchemaNode.FormatYaml(GetYamlData()); - } + return SchemaNode.FormatYaml(GetYamlData()); } } \ No newline at end of file diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs index 3f66376..1383c5a 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/ArraySchemaType.cs @@ -1,89 +1,88 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.SchemaBuilder.Types +namespace Pf2eDataScrapper.SchemaBuilder.Types; + +/// +/// Тип представоления массива в схеме. +/// +/// Элемент, к которому добавляется тип. +internal class ArraySchemaType(SchemaNode parent) : AbstractSchemaType(parent) { /// - /// Тип представоления массива в схеме. + /// Минимальное количество элементов в массиве. /// - /// Элемент, к которому добавляется тип. - internal class ArraySchemaType(SchemaNode parent) : AbstractSchemaType(parent) + public int MinCount = int.MaxValue; + + /// + /// Максимальное количество элементов в массиве. + /// + public int MaxCount = int.MinValue; + + /// + /// Схема элемента массива. + /// + protected SchemaNode ElementSchema = new(parent, "[]"); + + /// + /// Проверка, что значение совместимо с текущим типом. + /// + /// Проверяемое значение. + /// `true`, если значение совместимо с текущим типом. + public override bool IsCompatibleValue(JToken value) { - /// - /// Минимальное количество элементов в массиве. - /// - public int MinCount = int.MaxValue; + return Resolver.TokenType(value) == JTokenType.Array; + } - /// - /// Максимальное количество элементов в массиве. - /// - public int MaxCount = int.MinValue; - - /// - /// Схема элемента массива. - /// - protected SchemaNode ElementSchema = new(parent, "[]"); - - /// - /// Проверка, что значение совместимо с текущим типом. - /// - /// Проверяемое значение. - /// `true`, если значение совместимо с текущим типом. - public override bool IsCompatibleValue(JToken value) + /// + /// Добавление нового значения. + /// + /// Новое значение. + public override void AddValue(JToken value) + { + if (value.Type != JTokenType.Array) { - return Resolver.TokenType(value) == JTokenType.Array; + throw new Exception("Value is not an array, did you forget to call IsCompatibleValue?"); } - /// - /// Добавление нового значения. - /// - /// Новое значение. - public override void AddValue(JToken value) + JArray array = (JArray) value; + + MinCount = Math.Min(MinCount, array.Count); + MaxCount = Math.Max(MaxCount, array.Count); + + foreach (JToken item in array) { - if (value.Type != JTokenType.Array) - { - throw new Exception("Value is not an array, did you forget to call IsCompatibleValue?"); - } - - var array = (JArray)value; - - MinCount = Math.Min(MinCount, array.Count); - MaxCount = Math.Max(MaxCount, array.Count); - - foreach (var item in array) - { - ElementSchema.AddValue(item); - } - } - - /// - /// Подготовка типа к красивому представлению. - /// - public override void Beautify() - { - ElementSchema.Beautify(); - } - - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public override string GetTypeName() - { - return $"array<{ElementSchema.GetTypeName()}>"; - } - - /// - /// Получение данных для форматирования в YAML. - /// - /// Словарь данных - public override Dictionary GetYamlData() - { - return new() - { - ["min_count"] = MinCount.ToString(), - ["max_count"] = MaxCount.ToString(), - ["value_type"] = ElementSchema.ToString() - }; + ElementSchema.AddValue(item); } } + + /// + /// Подготовка типа к красивому представлению. + /// + public override void Beautify() + { + ElementSchema.Beautify(); + } + + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public override string GetTypeName() + { + return $"array<{ElementSchema.GetTypeName()}>"; + } + + /// + /// Получение данных для форматирования в YAML. + /// + /// Словарь данных + public override Dictionary GetYamlData() + { + return new() + { + ["min_count"] = MinCount.ToString(), + ["max_count"] = MaxCount.ToString(), + ["value_type"] = ElementSchema.ToString() + }; + } } diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs index 3c76338..ac3d0a5 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/DictionarySchemaType.cs @@ -1,77 +1,76 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.SchemaBuilder.Types +namespace Pf2eDataScrapper.SchemaBuilder.Types; + +/// +/// Тип представоления словаря в схеме. +/// +/// Элемент, к которому добавляется тип. +internal class DictionarySchemaType(SchemaNode parent) : AbstractSchemaType(parent) { /// - /// Тип представоления словаря в схеме. + /// Схема ключей словаря. /// - /// Элемент, к которому добавляется тип. - internal class DictionarySchemaType(SchemaNode parent) : AbstractSchemaType(parent) + public SchemaNode KeyNode = new(parent, "[key]"); + + /// + /// Схема значений словаря. + /// + public SchemaNode ItemNode = new(parent, "[value]"); + + /// + /// Проверка, что значение совместимо с текущим типом. + /// + /// Проверяемое значение. + /// `true`, если значение совместимо с текущим типом. + public override bool IsCompatibleValue(JToken value) { - /// - /// Схема ключей словаря. - /// - public SchemaNode KeyNode = new(parent, "[key]"); + return Resolver.TokenType(value) == JTokenType.Object; + } - /// - /// Схема значений словаря. - /// - public SchemaNode ItemNode = new(parent, "[value]"); + /// + /// Добавление нового значения. + /// + /// Новое значение. + public override void AddValue(JToken value) + { + JObject obj = (JObject) value; - /// - /// Проверка, что значение совместимо с текущим типом. - /// - /// Проверяемое значение. - /// `true`, если значение совместимо с текущим типом. - public override bool IsCompatibleValue(JToken value) + foreach (KeyValuePair pair in obj) { - return Resolver.TokenType(value) == JTokenType.Object; - } - - /// - /// Добавление нового значения. - /// - /// Новое значение. - public override void AddValue(JToken value) - { - var obj = (JObject)value; - - foreach (var pair in obj) - { - KeyNode.AddValue(pair.Key); - ItemNode.AddValue(pair.Value); - } - } - - /// - /// Подготовка типа к красивому представлению. - /// - public override void Beautify() - { - KeyNode.Beautify(); - ItemNode.Beautify(); - } - - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public override string GetTypeName() - { - return $"dict<{KeyNode.GetTypeName()}, {ItemNode.GetTypeName()}>"; - } - - /// - /// Получение данных для форматирования в YAML. - /// - /// Словарь данных - public override Dictionary GetYamlData() - { - return new Dictionary() - { - ["_key"] = KeyNode.ToString(), - ["_value"] = ItemNode.ToString() - }; + KeyNode.AddValue(pair.Key); + ItemNode.AddValue(pair.Value); } } + + /// + /// Подготовка типа к красивому представлению. + /// + public override void Beautify() + { + KeyNode.Beautify(); + ItemNode.Beautify(); + } + + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public override string GetTypeName() + { + return $"dict<{KeyNode.GetTypeName()}, {ItemNode.GetTypeName()}>"; + } + + /// + /// Получение данных для форматирования в YAML. + /// + /// Словарь данных + public override Dictionary GetYamlData() + { + return new Dictionary() + { + ["_key"] = KeyNode.ToString(), + ["_value"] = ItemNode.ToString() + }; + } } diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs index df025cd..c007c53 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/IgnoreSchemaType.cs @@ -1,55 +1,54 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.SchemaBuilder.Types +namespace Pf2eDataScrapper.SchemaBuilder.Types; + +/// +/// Тип, указывающий, что данные игнорируются. +/// +/// Элемент, к которому добавляется тип. +internal class IgnoreSchemaType(SchemaNode parent) : AbstractSchemaType(parent) { /// - /// Тип, указывающий, что данные игнорируются. + /// Проверка, что значение совместимо с текущим типом. /// - /// Элемент, к которому добавляется тип. - internal class IgnoreSchemaType(SchemaNode parent) : AbstractSchemaType(parent) + /// Проверяемое значение. + /// `true`, если значение совместимо с текущим типом. + public override bool IsCompatibleValue(JToken value) { - /// - /// Проверка, что значение совместимо с текущим типом. - /// - /// Проверяемое значение. - /// `true`, если значение совместимо с текущим типом. - public override bool IsCompatibleValue(JToken value) - { - return true; - } + return true; + } - /// - /// Добавление нового значения. - /// - /// Новое значение. - public override void AddValue(JToken value) - { - } + /// + /// Добавление нового значения. + /// + /// Новое значение. + public override void AddValue(JToken value) + { + } - /// - /// Подготовка типа к красивому представлению. - /// - public override void Beautify() - { - } + /// + /// Подготовка типа к красивому представлению. + /// + public override void Beautify() + { + } - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public override string GetTypeName() - { - return "ignore"; - } + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public override string GetTypeName() + { + return "ignore"; + } - /// - /// Получение данных для форматирования в YAML. - /// - /// Словарь данных - public override Dictionary GetYamlData() - { - return []; - } + /// + /// Получение данных для форматирования в YAML. + /// + /// Словарь данных + public override Dictionary GetYamlData() + { + return []; } } diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs index 5ff46ec..31db02e 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/ObjectSchemaType.cs @@ -1,124 +1,110 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.SchemaBuilder.Types +namespace Pf2eDataScrapper.SchemaBuilder.Types; + +/// +/// Тип представоления объектов в схеме. +/// +/// Элемент, к которому добавляется тип. +internal class ObjectSchemaType(SchemaNode parent) : AbstractSchemaType(parent) { /// - /// Тип представоления объектов в схеме. + /// Элемнты полей объекта. /// - /// Элемент, к которому добавляется тип. - internal class ObjectSchemaType(SchemaNode parent) : AbstractSchemaType(parent) + public List Fields = []; + + /// + /// Проверка, что значение совместимо с текущим типом. + /// + /// Проверяемое значение. + /// `true`, если значение совместимо с текущим типом. + public override bool IsCompatibleValue(JToken value) { - /// - /// Элемнты полей объекта. - /// - public List Fields = []; + return Resolver.TokenType(value) == JTokenType.Object; + } - /// - /// Проверка, что значение совместимо с текущим типом. - /// - /// Проверяемое значение. - /// `true`, если значение совместимо с текущим типом. - public override bool IsCompatibleValue(JToken value) + /// + /// Добавление нового значения. + /// + /// Новое значение. + public override void AddValue(JToken value) + { + JObject obj = (JObject) value; + + foreach (SchemaNode field in Fields) { - return Resolver.TokenType(value) == JTokenType.Object; - } - - /// - /// Добавление нового значения. - /// - /// Новое значение. - public override void AddValue(JToken value) - { - var obj = (JObject)value; - - foreach (var field in Fields) + if (!obj.ContainsKey(field.Name)) { - if (!obj.ContainsKey(field.Name)) - { - field.IsNullable = true; - } - } - - foreach (var pair in obj) - { - var field = Fields.FirstOrDefault(f => f.Name == pair.Key); - - if (field is null) - { - field = new SchemaNode(Parent, pair.Key); - Fields.Add(field); - } - - field.AddValue(pair.Value); + field.IsNullable = true; } } - /// - /// Подготовка типа к красивому представлению. - /// - public override void Beautify() + foreach (KeyValuePair pair in obj) { - Fields.Sort((a, b) => + SchemaNode? field = Fields.FirstOrDefault(f => f.Name == pair.Key); + + if (field is null) { - if (a.IsNullable && !b.IsNullable) - { - return 0; - } - - return string.Compare(a.Name, b.Name, StringComparison.Ordinal); - }); - - foreach (var field in Fields) - { - field.Beautify(); - } - } - - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public override string GetTypeName() - { - return "object"; - } - - /// - /// Получение данных для форматирования структуры в YAML. - /// - /// Словарь данных - public Dictionary GetSchemaYaml() - { - var result = new Dictionary(); - - foreach (var field in Fields) - { - var s = field.Name; - if (field.IsNullable) - { - s = $"[{s}]"; - } - else - { - s = $"'{s}'"; - } - result.Add(s, field.ToString()); + field = new SchemaNode(Parent, pair.Key); + Fields.Add(field); } - return result; - } - - /// - /// Получение данных для форматирования в YAML. - /// - /// Словарь данных - public override Dictionary GetYamlData() - { - return new() - { - ["schema"] = SchemaNode.FormatYaml(GetSchemaYaml()) - }; + field.AddValue(pair.Value); } } + /// + /// Подготовка типа к красивому представлению. + /// + public override void Beautify() + { + Fields.Sort((a, b) => + { + return a.IsNullable && !b.IsNullable ? 0 : string.Compare(a.Name, b.Name, StringComparison.Ordinal); + }); + + foreach (SchemaNode field in Fields) + { + field.Beautify(); + } + } + + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public override string GetTypeName() + { + return "object"; + } + + /// + /// Получение данных для форматирования структуры в YAML. + /// + /// Словарь данных + public Dictionary GetSchemaYaml() + { + Dictionary result = new(); + + foreach (SchemaNode field in Fields) + { + string s = field.Name; + s = field.IsNullable ? $"[{s}]" : $"'{s}'"; + result.Add(s, field.ToString()); + } + + return result; + } + + /// + /// Получение данных для форматирования в YAML. + /// + /// Словарь данных + public override Dictionary GetYamlData() + { + return new() + { + ["schema"] = SchemaNode.FormatYaml(GetSchemaYaml()) + }; + } } diff --git a/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs b/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs index 01df186..67b870c 100644 --- a/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs +++ b/Pf2eDataScrapper/SchemaBuilder/Types/StaticSchemaType.cs @@ -1,102 +1,101 @@ using Newtonsoft.Json.Linq; -namespace Pf2eDataScrapper.SchemaBuilder.Types +namespace Pf2eDataScrapper.SchemaBuilder.Types; + +/// +/// Тип представоления простых значений в схеме. +/// +/// Элемент, к которому добавляется тип. +/// Тип значения. +internal class StaticSchemaType(SchemaNode parent, JTokenType type) : AbstractSchemaType(parent) { /// - /// Тип представоления простых значений в схеме. + /// Тип значения. /// - /// Элемент, к которому добавляется тип. - /// Тип значения. - internal class StaticSchemaType(SchemaNode parent, JTokenType type) : AbstractSchemaType(parent) + protected JTokenType Type = type; + + /// + /// Набор возможных значений и их количества. + /// + protected Dictionary Values = []; + + /// + /// Проверка, что значение совместимо с текущим типом. + /// + /// Проверяемое значение. + /// `true`, если значение совместимо с текущим типом. + public override bool IsCompatibleValue(JToken value) { - /// - /// Тип значения. - /// - protected JTokenType Type = type; + return value.Type == Type; + } - /// - /// Набор возможных значений и их количества. - /// - protected Dictionary Values = []; + /// + /// Добавление нового значения. + /// + /// Новое значение. + public override void AddValue(JToken value) + { + Values.TryAdd(value, 0); - /// - /// Проверка, что значение совместимо с текущим типом. - /// - /// Проверяемое значение. - /// `true`, если значение совместимо с текущим типом. - public override bool IsCompatibleValue(JToken value) + Values[value]++; + } + + /// + /// Подготовка типа к красивому представлению. + /// + public override void Beautify() + { + if (Type == JTokenType.String) { - return value.Type == Type; - } + Dictionary newValues = new(); - /// - /// Добавление нового значения. - /// - /// Новое значение. - public override void AddValue(JToken value) - { - Values.TryAdd(value, 0); - - Values[value]++; - } - - /// - /// Подготовка типа к красивому представлению. - /// - public override void Beautify() - { - if (Type == JTokenType.String) + foreach (KeyValuePair pair in Values) { - var newValues = new Dictionary(); - - foreach (var pair in Values) - { - var newValue = "\"" + pair.Key.ToString().Trim().Replace("\"", "\\\"") + "\""; - newValues.TryAdd(newValue, 0); - newValues[newValue] += pair.Value; - } - - Values = newValues; + string newValue = "\"" + pair.Key.ToString().Trim().Replace("\"", "\\\"") + "\""; + newValues.TryAdd(newValue, 0); + newValues[newValue] += pair.Value; } - Values = Values.OrderByDescending(pair => pair.Value).ToDictionary(pair => pair.Key, pair => pair.Value); + Values = newValues; } - /// - /// Получение строкового представоения типа элемента. - /// - /// Строковое представоение типа элемента - public override string GetTypeName() + Values = Values.OrderByDescending(pair => pair.Value).ToDictionary(pair => pair.Key, pair => pair.Value); + } + + /// + /// Получение строкового представоения типа элемента. + /// + /// Строковое представоение типа элемента + public override string GetTypeName() + { + return Type.ToString().ToLower(); + } + + /// + /// Получение данных для форматирования в YAML. + /// + /// Словарь данных + public override Dictionary GetYamlData() + { + List values = []; + int count = Values.Count; + + string prefix = (count == 1) ? "" : "- "; + + if (count is <= SchemaNode.ElementLimit and > 0 && (Type != JTokenType.String || !Values.Any(s => s.ToString().Contains('\n')))) { - return Type.ToString().ToLower(); - } - - /// - /// Получение данных для форматирования в YAML. - /// - /// Словарь данных - public override Dictionary GetYamlData() - { - var values = new List(); - var count = Values.Count; - - var prefix = (count == 1) ? "" : "- "; - - if (count is <= SchemaNode.ElementLimit and > 0 && (Type != JTokenType.String || !Values.Any(s => s.ToString().Contains('\n')))) + foreach (KeyValuePair kvp in Values) { - foreach (var kvp in Values) - { - values.Add($"{prefix}{kvp.Key} (x{kvp.Value})"); - } + values.Add($"{prefix}{kvp.Key} (x{kvp.Value})"); } - - return new() - { - ["different_values"] = count.ToString() + - ((count == Root.Values) ? " (all different)" : "") + - ((count == 1) ? " (all same)" : ""), - ["variants"] = (values.Count > 0) ? string.Join("\r\n", values) : "[omitted]" - }; } + + return new() + { + ["different_values"] = count.ToString() + + ((count == Root.Values) ? " (all different)" : "") + + ((count == 1) ? " (all same)" : ""), + ["variants"] = (values.Count > 0) ? string.Join("\r\n", values) : "[omitted]" + }; } }