diff --git a/.editorconfig b/.editorconfig index 473207c5..477a603e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,12 +3,22 @@ root = true # All files [*] indent_style = space -dotnet_diagnostic.CA1047.severity = error # Xml files [*.xml] indent_size = 2 +# Xml project files +[*.{csproj,fsproj,vbproj,proj,slnx}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +[*.json] +indent_size = 2 + # C# files [*.cs] @@ -19,7 +29,6 @@ indent_size = 4 tab_width = 4 # New line preferences -end_of_line = crlf insert_final_newline = false #### .NET Coding Conventions #### @@ -53,13 +62,16 @@ dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent dotnet_style_coalesce_expression = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_object_initializer = true:suggestion dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion dotnet_style_prefer_compound_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion @@ -79,15 +91,15 @@ dotnet_remove_unnecessary_suppression_exclusions = none [*.cs] # var preferences -csharp_style_var_elsewhere = false:silent +csharp_style_var_elsewhere = false:suggestion csharp_style_var_for_built_in_types = false:silent -csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_when_type_is_apparent = true:suggestion # Expression-bodied members csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_lambdas = when_on_single_line:error +csharp_style_expression_bodied_lambdas = true:suggestion csharp_style_expression_bodied_local_functions = false:silent csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_operators = false:silent @@ -96,6 +108,7 @@ csharp_style_expression_bodied_properties = true:silent # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion csharp_style_prefer_not_pattern = true:suggestion csharp_style_prefer_pattern_matching = true:silent csharp_style_prefer_switch_expression = true:suggestion @@ -104,20 +117,31 @@ csharp_style_prefer_switch_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences +csharp_prefer_static_anonymous_function = true:suggestion csharp_prefer_static_local_function = true:warning -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent +csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion # Code-block preferences csharp_prefer_braces = true:silent csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent # Expression-level preferences 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_pattern_local_over_anonymous_function = 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_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 @@ -171,32 +195,21 @@ csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true -csharp_style_namespace_declarations = file_scoped:error -csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_top_level_statements = true:silent -csharp_style_prefer_primary_constructors = false:suggestion -csharp_style_prefer_null_check_over_type_check = true:suggestion -csharp_style_prefer_local_over_anonymous_function = true:suggestion -csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion -csharp_style_prefer_tuple_swap = true:suggestion -csharp_style_prefer_utf8_string_literals = true:suggestion -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent -csharp_style_prefer_readonly_struct = true:suggestion -csharp_style_prefer_readonly_struct_member = true:suggestion -dotnet_diagnostic.CA1070.severity = error -dotnet_diagnostic.CA1032.severity = error -dotnet_diagnostic.CA1865.severity = error -dotnet_diagnostic.CA1866.severity = error -dotnet_diagnostic.CA1867.severity = error -dotnet_diagnostic.CA2014.severity = error csharp_prefer_system_threading_lock = true:suggestion -csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion -csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion csharp_style_prefer_simple_property_accessors = true:suggestion +csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion +csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +dotnet_diagnostic.CA1003.severity = error +dotnet_diagnostic.CA1008.severity = error +dotnet_diagnostic.CA1032.severity = error +dotnet_diagnostic.CA1034.severity = silent +dotnet_diagnostic.CA1036.severity = error +dotnet_diagnostic.CA1044.severity = error #### Naming styles #### [*.{cs,vb}] @@ -388,32 +401,17 @@ dotnet_naming_style.s_camelcase.required_prefix = s_ dotnet_naming_style.s_camelcase.required_suffix = dotnet_naming_style.s_camelcase.word_separator = dotnet_naming_style.s_camelcase.capitalization = camel_case -dotnet_style_prefer_collection_expression = when_types_exactly_match:suggestion -dotnet_style_namespace_match_folder = true:error tab_width = 4 indent_size = 4 end_of_line = crlf -dotnet_diagnostic.CA1000.severity = silent -dotnet_diagnostic.CA1001.severity = warning -dotnet_diagnostic.CA1002.severity = suggestion -dotnet_diagnostic.CA1008.severity = error -dotnet_diagnostic.CA1061.severity = error -dotnet_diagnostic.CA1063.severity = error -dotnet_diagnostic.CA2000.severity = error -dotnet_diagnostic.CA1816.severity = error -dotnet_diagnostic.CA2213.severity = error -dotnet_diagnostic.CA2215.severity = error -dotnet_diagnostic.CA1064.severity = error -dotnet_diagnostic.CA1069.severity = error -dotnet_diagnostic.CA1401.severity = error -dotnet_diagnostic.CA1501.severity = error -dotnet_diagnostic.CA1700.severity = error -dotnet_diagnostic.CA1821.severity = error -dotnet_diagnostic.CA1836.severity = error -dotnet_diagnostic.CA1843.severity = error -dotnet_diagnostic.CA1842.severity = error -dotnet_diagnostic.CA2011.severity = error dotnet_style_allow_multiple_blank_lines_experimental = true:silent dotnet_style_allow_statement_immediately_after_block_experimental = true:silent insert_final_newline = true +dotnet_diagnostic.CA1001.severity = error +dotnet_diagnostic.CA1002.severity = silent +dotnet_diagnostic.CA1005.severity = error +dotnet_diagnostic.CA1010.severity = error +dotnet_diagnostic.CA1041.severity = error +dotnet_diagnostic.CA2025.severity = error +dotnet_diagnostic.CA2213.severity = error diff --git a/BookGen.slnx b/BookGen.slnx index f5b834a6..e45768c8 100644 --- a/BookGen.slnx +++ b/BookGen.slnx @@ -13,7 +13,6 @@ - diff --git a/Commands.md b/Commands.md index 72748c9f..30923a51 100644 --- a/Commands.md +++ b/Commands.md @@ -2,8 +2,7 @@ BookGen - Markdown to Book tool. -For the tool to work in the work folder there must be a bookgen.json config file. This config file -can be created with the following command: +For the tool to work in the work folder there must be a bookgen.json config file. This config file can be created with the following command: `BookGen Newbook` @@ -17,500 +16,608 @@ To list available subcommands type: General arguments: -`-wd` -`--wait-debugger` - Waits for a debugger to be attached. Usefull for error reporting & error finding. +* `-wd` or `--wait-debugger` -`-ad` -`--attach-debugger` - Attaches a debugger. Usefull for error reporting & error finding. + Waits for a debugger to be attached. Usefull for error reporting & error finding. -`-js` -`--json-log` - Outputs log in JSON format. Usefull for interop purposes. +* `-ad` or `--attach-debugger` + + Attaches a debugger. Usefull for error reporting & error finding. + +* `-js` or `--json-log` + + Outputs log in JSON format. Usefull for interop purposes. # Addfrontmatter Add a basic YAML frontmatter information to all markdown files located in the current folder and it's subfolders. -`BookGen Addfrontmatter [-v] [-d [directory]]` -`BookGen Addfrontmatter [--verbose] [--dir [directory]]` +``` +BookGen Addfrontmatter [-v] [-d [directory]] +BookGen Addfrontmatter [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then - the current directory will be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # Assembly-document Generates a markdown file(s) from a given .NET assembly and it's XML documentation file. -`BookGen Assembly-document -i -o [-d] [-n]` -`BookGen Assembly-document --input --output [--dry] [--namespace-pages]` +``` +BookGen Assembly-document -i -o [-d] [-n] +BookGen Assembly-document --input --output [--dry] [--namespace-pages] +``` Arguments: --i, --input: - Required argument. Specifies the input assembly file path. The file must be a .NET assembly. +* `-i`, `--input`: + + Required argument. Specifies the input assembly file path. The file must be a .NET assembly. + +* `-o`, `--output`: + + Required argument. Specifies the output files path. + +* `-d`, `--dry`: --o, --output: - Required argument. Specifies the output files path. + Optional argument. If specified, the command will not write any files, but will only print the output to console. --d, --dry: - Optional argument. If specified, the command will not write any files, - but will only print the output to console. +* `-n`, `--namespace-pages`: --n, --namespace-pages: - Optional argument. If specified, the command will create a separate markdown file for each - namespace in the assembly. + Optional argument. If specified, the command will create a separate markdown file for each namespace in the assembly. # BuildEpub Build an epub3 file from the book. -`BookGen BuildEpub -o [-v] [-d [directory]]` -`BookGen BuildEpub --output [--verbose] [--dir [directory]]` +``` +BookGen BuildEpub -o [-v] [-d [directory]] +BookGen BuildEpub --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # BuildExport Build a JSON file with schema for post processing of the book. -`BookGen BuildExport -o [-v] [-d [directory]]` -`BookGen BuildExport --output [--verbose] [--dir [directory]]` +``` +BookGen BuildExport -o [-v] [-d [directory]] +BookGen BuildExport --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-h`, `--host`: + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # BuildFeed Build an RSS 2.0 and an Atom 1.0 feed from the book. -`BookGen BuildFeed -o [-v] [-d [directory]]` -`BookGen BuildFeed --output [--verbose] [--dir [directory]]` +``` +BookGen BuildFeed -o [-v] [-d [directory]] +BookGen BuildFeed --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # BuildPrint Build a printable html & xhtml file from the book -`BookGen BuildPrint -o [-v] [-d [directory]]` -`BookGen BuildPrint --output [--verbose] [--dir [directory]]` +``` +BookGen BuildPrint -o [-v] [-d [directory]] +BookGen BuildPrint --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-h`, `--host`: + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # BuildWeb Build a static website from the book -`BookGen BuildWeb -o [-v] [-d [directory]]` -`BookGen BuildWeb --output [--verbose] [--dir [directory]]` +``` +BookGen BuildWeb -o [-v] [-d [directory]] +BookGen BuildWeb --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the output directory name. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. +* `-h`, `--host`: + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # BuildWp Build a wordpress export file from the book. -`BookGen BuildWp -o [-v] [-d [directory]]` -`BookGen BuildWp --output [--verbose] [--dir [directory]]` +``` +BookGen BuildWp -o [-v] [-d [directory]] +BookGen BuildWp --output [--verbose] [--dir [directory]] +``` Arguments: --o, --output: - Required argument. Specifies the output directory name. +* `-o`, `--output`: + + Required argument. Specifies the output directory name. --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues --h, --host: - Optional argument. If specified, the host name set in the config file will be ignored and the - host name will be set to the specified value. +* `-h`, `--host`: + + + Optional argument. If specified, the host name set in the config file will be ignored and the host name will be set to the specified value. # Config Get or set bookgen application specific settings -`BookGen Config` - List all currently supported application wide settings +* `BookGen Config` + + List all currently supported application wide settings + +* `BookGen Config ` -`BookGen Config ` - Gets a setting value, prints it to output and exits. + Gets a setting value, prints it to output and exits. -`BookGen Config ` - Sets a setting value and exits +* `BookGen Config ` + + Sets a setting value and exits # Edit Open a file for editing with configured editor. +`BookGen edit [filename]` + # Gui Starts the program with a command line gui interface - -`BookGen Gui [-v] [-d [directory]]` -`BookGen Gui [--verbose] [--dir [directory]]` +``` +BookGen Gui [-v] [-d [directory]] +BookGen Gui [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. -`BookGen edit [filename]` +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues. # Html2Pdf Converts a HTML file to a png using edges or chromes headless mode. The tool will use chrome, if it's installed, otherwise it will use edge. This command is only supported on Windows OS. -`BookGen Html2Pdf -i -o ` -`BookGen Html2Pdf --input --output ` +``` +BookGen Html2Pdf -i -o +BookGen Html2Pdf --input --output +``` --i, --input: - Input html file with extension of .htm or .html +* `-i`, `--input`: --o, --output: - Output PDF file. + Input html file with extension of .htm or .html + +* `-o`, `--output`: + + Output PDF file. # Html2Png Converts a HTML file to a png using edges or chromes headless mode. The tool will use chrome, if it's installed, otherwise it will use edge. This command is only supported on Windows OS. -`BookGen Html2Png -i -o [-w [width]] [-h [height]]` -`BookGen Html2Png --input --output [--width [width]] [--height [height]]` +``` +BookGen Html2Png -i -o [-w [width]] [-h [height]] +BookGen Html2Png --input --output [--width [width]] [--height [height]] +``` + +* `-i`, `--input`: + + Input html file with extension of .htm or .html --i, --input: - Input html file with extension of .htm or .html +* `-o`, `--output`: --o, --output: - Output PNG file. + Output PNG file. --w, --width: - Optional argument. Specifies the width of the output image in pixels. +* `-w`, `--width`: --h, --height: - Optional argument. Specifies the height of the output image in pixels. + Optional argument. Specifies the width of the output image in pixels. + +* `-h`, `--height`: + + Optional argument. Specifies the height of the output image in pixels. # ImgConvert Converts an image file to a different format. The tool supports png, jpeg, webp and svg formats. -`BookGen ImgConvert -i -o -f [-q [quality]] [-r [resolution]]` +``` +BookGen ImgConvert -i -o -f [-q [quality]] [-r [resolution]] +``` Arguments: --i, --input: - Required argument. Specifies the input image file path. The file must be a valid image file or - a directory containing image files. +* `-i`, `--input`: + + Required argument. Specifies the input image file path. The file must be a valid image file or a directory containing image files. + +* `-o`, `--output`: --o, --output: - Required argument. Specifies the output file path. The file must have a valid image file - extension, like .png, .jpg, .jpeg, .webp. Can also be a directory, in which case the output - files will be saved with the same name as the input files + Required argument. Specifies the output file path. The file must have a valid image file extension, like .png, .jpg, .jpeg, .webp. Can also be a directory, in which case the output files will be saved with the same name as the input files + +* `-f`, `--format`: --f, --format: Required argument. Specifies the output image format. Supported formats are png, jpeg, webp. --q, --quality: - Optional argument. Specifies the quality of the output image. The value must be between - 0 and 100. Default is 90. If not specified, then the default value will be used. +* `-q`, `--quality`: + + Optional argument. Specifies the quality of the output image. The value must be between 0 and 100. Default is 90. If not specified, then the default value will be used. --r, --resolution: - Optional argument. Specifies the resolution of the output image. The value must be a valid - resolution string, like 1920x1080 or 1280x720. If not specified, then the resolution will be - the same as the input image. +* `-r`, `--resolution`: + + Optional argument. Specifies the resolution of the output image. The value must be a valid resolution string, like 1920x1080 or 1280x720. If not specified, then the resolution will be the same as the input image. # Install Windows only command that installs BookGen to the system PATH & optionally to the windows terminal. -`BookGen Install` +``` +BookGen Install +``` # JsonArgs Creates an empty json arguments template file for a given bookgen command. -`BookGen JsonArgs -c [-d [directory]]` -`BookGen JsonArgs --command [--dir [directory]]` +``` +BookGen JsonArgs -c [-d [directory]] +BookGen JsonArgs --command [--dir [directory]] +``` -A Json arguments template can be used to store command line arguments, so the bookgen command can -be invoked with the same arguments without having to type them in again. +A Json arguments template can be used to store command line arguments, so the bookgen command can be invoked with the same arguments without having to type them in again. Arguments: --c, --command: - Required argument. Specifies the command for which the json template will be created. +* `-c`, `--command`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Required argument. Specifies the command for which the json template will be created. + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # Links Scans all markdown files in the current book and writes the lins to a markdown file, named links.md -`BookGen Links [-v] [-d [directory]]` -`BookGen Links [--verbose] [--dir [directory]]` +``` +BookGen Links [-vf] [-v] [-d [directory]] +BookGen Links [--verify] [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-vf`, `--verify`: + + Optional argument. When specified, the command will verify if the links are accessible and will print the result to the console. If not specified, the command will only write the links to the output file. # Math2Svg Renders a single markdown file containing Tex formulas to svg files -`BookGen Math2Svg -f -o [-s [scale]]` -`BookGen Math2Svg --formula --output [--scale [scale]]` +``` +BookGen Math2Svg -f -o [-s [scale]] +BookGen Math2Svg --formula --output [--scale [scale]] +``` Arguments: --f, --formula: - Formula to render to svg. The formula must be a valid Tex formula. +* `-f`, `--formula`: + + Formula to render to svg. The formula must be a valid Tex formula. --o, --output: - Output svg file. +* `-o`, `--output`: --s, --scale: - Optional argument. Specifies the scale of the output svg file. Default is 1.0. - If not specified, then the default value will be used. + Output svg file. + +* `-s`, `--scale`: + + Optional argument. Specifies the scale of the output svg file. Default is 1.0. If not specified, then the default value will be used. # Md2HTML Renders a single markdown file to an HTML file -`BookGen Md2HTML -i -o ` -`BookGen Md2HTML --input --output ` +``` +BookGen Md2HTML -i -o +BookGen Md2HTML --input --output +``` Arguments: --i, --input: - Input markdown file path. Multiple files can be set with multiple - -i arguments +* `-i`, `--input`: + + Input markdown file path. Multiple files can be set with multiple `-i` arguments + +* `-o`, `--output`: + + Output html file path. If file name is "-", outputs to console. + +* `-tf`, `--template` + + Optional argument. If not specified, default template is used. If custom file provided, then the file must contain the folloing tags: + + * `` - For document title + * `` - For document content + +* `-ns`, `--no-syntax` + + Optional argument. Disables syntax highlighting. + +* `-ne`, `--no-embed` --o, --output: - Output html file path. If file name is "con", outputs to console. + Optional argument. Disables embedding of media site links, like youtube. --tf, --template - Optional argument. If not specified, default template is used. If custom file provided, then - the file must contain the folloing tags: - `` - For document title - `` - For document content +* `-r`, `--raw` --ns, --no-syntax - Optional argument. Disables syntax highlighting. + Optional argument. Disables full html generation, only outputs the html produced by the markdown formatting. --ne, --no-embed - Optional argument. Disables embedding of media site links, - like youtube. +* `-s`, `--svg` --r, --raw - Optional argument. Disables full html generation, only outputs the html produced by - the markdown formatting. + Enables SVG Passthrough. When enabled SVG files will be embedded in resulting html, instead of being rendered to webp. --s, --svg - Enables SVG Passthrough. When enabled SVG files will be embedded in resulting html, instead - of being rendered to webp. +* `-t`, `--title` --t, --title - Optional argument. Specifies the rendered HTML page title. Only has affect, when -r or --raw - is not specified. + Optional argument. Specifies the rendered HTML page title. Only has affect, when `-r` or `--raw` is not specified. + +# Md2terminal + +Converts a markdown file to terminal formatted text. + +``` +BookGen Md2terminal -i -o +BookGen Md2terminal --input --output +``` + +* `-i`, `--input`: + + Input markdown file path. Multiple files can be set with multiple `-i` arguments + +* `-o`, `--output`: + + Output html file path. If file name is "-", outputs to console. # Migrate Migrate an old Bookgen book to the new format. -`BookGen Migrate [-v] [-d [directory]]` -`BookGen Migrate [--verbose] [--dir [directory]]` +``` +BookGen Migrate [-v] [-d [directory]] +BookGen Migrate [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # NewBook Creates a new book structure in the given folder -`BookGen NewBook [-v] [-d [directory]]` -`BookGen NewBook [--verbose] [-dir [directory]]` +``` +BookGen NewBook [-v] [-d [directory]] +BookGen NewBook [--verbose] [-dir [directory]] +``` Arguments: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # NewPage Creates a new markdown page. -`BookGen NewPage -n [-v] [-d [directory]]` -`BookGen NewPage --name [--verbose] [-dir [directory]]` +``` +BookGen NewPage -n [-v] [-d [directory]] +BookGen NewPage --name [--verbose] [-dir [directory]] +``` Arguments: --n, --name: - File name. Specifies new file name +* `-n`, `--name`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + File name. Specifies new file name --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues + +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # QrCode Renders an url into a QRCode image -`BookGen QrCode -d -o [-c [color]]` -`BookGen QrCode --data --output [--color [color]]` +``` +BookGen QrCode -d -o [-c [color]] +BookGen QrCode --data --output [--color [color]] +``` Arguments: --d, --data: - Url data to encode. Minimum 1 byte, Maximum 900 bytes +* `-d`, `--data`: + + Url data to encode. Minimum 1 byte, Maximum 900 bytes + +* `-c`, `--color`: --c, --color: - Optional argument. Specifies the color of the QRCode. The color must be a valid hex color code, - like #FF0000 or #F00. + Optional argument. Specifies the color of the QRCode. The color must be a valid hex color code, like #FF0000 or #F00. --o, --output - Output file. Must have .png or .svg extension +* `-o`, `--output` + + Output file. Must have .png or .svg extension # Schemas Creates a schemas.md documentation file, describing the various config schemas used by bookgen. -`BookGen Schemas [-v] [-d [directory]]` -`BookGen Schemas [--verbose] [--dir [directory]]` +``` +BookGen Schemas [-v] [-d [directory]] +BookGen Schemas [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # Serve Starts a local only http server that serves file from the given directory -`BookGen Serve [-d [directory]]` -`BookGen Serve [--dir [directory]]` +``` +BookGen Serve [-d [directory]] +BookGen Serve [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. # Shell Autocompleter command, that is used by Powershell -`BookGen Shell` +``` +BookGen Shell +``` # Shortcut -Create a cmd file in the current directory that can be used to start the bookgen Shell in the -current directory. +Create a cmd file in the current directory that can be used to start the bookgen Shell in the current directory. -`BookGen Shortcut` +``` +BookGen Shortcut +``` This command is only supported on Windows OS. @@ -518,64 +625,74 @@ This command is only supported on Windows OS. Displays various statistics about the bookgen project. -`BookGen Stats [-v] [-d [directory]]` -`BookGen Stats [--verbose] [--dir [directory]]` +``` +BookGen Stats [-v] [-d [directory]] +BookGen Stats [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # Subcommands Listst all available subcommands -`BookGen SubCommands` +``` +BookGen SubCommands +``` # Templates -`Bookgen Templates [-n [template name]]` -`Bookgen Templates [--name [template name]]` - Lists all available templates, or extracts a single template to the current directory. +``` +Bookgen Templates [-n [template name]] +Bookgen Templates [--name [template name]] +``` + Arguments: --n, --name: - Optional argument. If specified, only the template with the given name will be extracted. - If not specified, all available templates will be printed. +* `-n`, `--name`: + + Optional argument. If specified, only the template with the given name will be extracted. If not specified, all available templates will be printed. # Terminalinstall Installs a bookgen profile to the Windows Termninal. -This command is only supported on Windows OS. +This command is only supported on Windows OS. Without arguments, performs terminal profile install. -`BookGen Terminalinstall [-c] [-t]` -`BookGen Terminalinstall [--checkinstall] [--checkterminalinstall]` +``` +BookGen Terminalinstall [-c] [-t] +BookGen Terminalinstall [--checkinstall] [--checkterminalinstall] +``` Arguments: --c, --checkinstall: - Optional argument. When specified checks, if terminal profile installed or not. If exit code - is 0, profile is installed. +* `-c`, `--checkinstall`: + + Optional argument. When specified checks, if terminal profile installed or not. If exit code is 0, profile is installed. --t, --checkterminalinstall: - Optional argument. When specified checks, if windows terminal is installed or not. If exit code - is 0, terminal is installed. +* `-t`, `--checkterminalinstall`: -Without arguments, performs terminal profile install. + Optional argument. When specified checks, if windows terminal is installed or not. If exit code is 0, terminal is installed. # Tools -Display a list of downloadable tools that can be installed and used with BookGen shell. This -command is only supported on Windows OS. +Display a list of downloadable tools that can be installed and used with BookGen shell. -`BookGen Tools` +This command is only supported on Windows OS. + +``` +BookGen Tools +``` # Upgrade @@ -583,34 +700,40 @@ Upgrades the bookgen project to the latest version. This command will upgrade th config file to the latest version, and will also upgrade the bookgen.toc.json file to the latest version. -`BookGen Upgrade [-v] [-d [directory]]` -`BookGen Upgrade [--verbose] [--dir [directory]]` +``` +BookGen Upgrade [-v] [-d [directory]] +BookGen Upgrade [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then - the current directory will be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues +* `-v`, `--verbose`: + + Optional argument, turns on detailed logging. Usefull for locating issues # Validate Validate the configuration files used by bookgen in the specified folder. -`BookGen Validate [-v] [-d [directory]]` -`BookGen Validate [--verbose] [--dir [directory]]` +``` +BookGen Validate [-v] [-d [directory]] +BookGen Validate [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory will - be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues # Version @@ -618,19 +741,21 @@ Print the current program and config API version `BookGen Version` - # Vstasks Generates a Visual Studio Code tasks.json file for the bookgen project. -`BookGen Vstasks [-v] [-d [directory]]` -`BookGen Vstasks [--verbose] [--dir [directory]]` +``` +BookGen Vstasks [-v] [-d [directory]] +BookGen Vstasks [--verbose] [--dir [directory]] +``` Arguments: --d, --dir: - Optional argument. Specifies work directory. If not specified, then the current directory - will be used as working directory. +* `-d`, `--dir`: + + Optional argument. Specifies work directory. If not specified, then the current directory will be used as working directory. + +* `-v`, `--verbose`: --v, --verbose: - Optional argument, turns on detailed logging. Usefull for locating issues + Optional argument, turns on detailed logging. Usefull for locating issues diff --git a/Directory.Packages.props b/Directory.Packages.props index 0aff0ee3..aec8df1b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,27 +5,27 @@ - + - - - - + + + + - - - - + + + + - - + + diff --git a/Installers/debian-info.txt b/Installers/debian-info.txt deleted file mode 100644 index 95c8a9ab..00000000 --- a/Installers/debian-info.txt +++ /dev/null @@ -1,2 +0,0 @@ -Debian user: user -Debian password: pass \ No newline at end of file diff --git a/Installers/install.sh b/Installers/install.sh deleted file mode 100644 index 3100d2ee..00000000 --- a/Installers/install.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -#check for admin rights -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - exit -fi - -mkdir -p /opt/bookgen -cp bin/* /opt/bookgen -ln -s /opt/bookgen/BookGen /usr/bin/bookgen - -echo "BookGen installed successfully!" \ No newline at end of file diff --git a/Installers/prepare-debian-base.sh b/Installers/prepare-debian-base.sh deleted file mode 100644 index 8506a236..00000000 --- a/Installers/prepare-debian-base.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -#check for admin rights -if [ "$EUID" -ne 0 ] - then echo "Please run as root" - exit -fi - -apt update -apt dist-upgrade -y -apt install -y curl wget mc - -wget https://packages.microsoft.com/config/debian/13/packages-microsoft-prod.deb -O packages-microsoft-prod.deb -sudo dpkg -i packages-microsoft-prod.deb -rm packages-microsoft-prod.deb - -apt update -apt install -y dotnet-sdk-10.0 -apt autoremove --purge -apt clean \ No newline at end of file diff --git a/Source/BookGen.Cli/packages.lock.json b/Source/BookGen.Cli/packages.lock.json index e1bba11c..f260753c 100644 --- a/Source/BookGen.Cli/packages.lock.json +++ b/Source/BookGen.Cli/packages.lock.json @@ -4,17 +4,17 @@ "net10.0": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "bookgen.vfs": { diff --git a/Source/BookGen.Contents/BookGenShell.ps1 b/Source/BookGen.Contents/BookGenShell.ps1 index 66978089..af7ef4a3 100644 --- a/Source/BookGen.Contents/BookGenShell.ps1 +++ b/Source/BookGen.Contents/BookGenShell.ps1 @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BookGen PowerShell Registration script -# Version 3.7.2 -# Last modified: 2025-10-05 +# Version 3.8.0 +# Last modified: 2025-11-16 # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- @@ -173,6 +173,15 @@ function gh { GetTool "gh" "github-cli" @Args } +function glow { + param ( + [string[]] + [Parameter(ValueFromRemainingArguments = $true)] + $Args + ) + GetTool "glow" "glow" @Args +} + function copyparty { param ( [string[]] @@ -252,14 +261,14 @@ function weather { [string]$Location ) - $url = "https://wttr.in/" + $url = "https://wttr.in/?n" if ($PSBoundParameters.ContainsKey('Location')) { $url += $Location } Clear-Host - curl $url + (Invoke-WebRequest $url).Content } # intro message @@ -403,11 +412,14 @@ Register-ArgumentCompleter -Native -CommandName git -ScriptBlock { # set prompt function prompt { $git = $(BookGen.Shellprog.exe "prompt" $(Get-Location).Path) + $location = (Get-Location).Path + $topLine = "╭╴$location"+"`n" + if (-not [string]::IsNullOrWhiteSpace($git)) { - 'PS ' + $(Get-Location) + "`n" + $git + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' + $topLine + '╰╴ PS ' + $git + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' } else { - 'PS ' + $(Get-Location) + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' + $topLine + '╰╴ PS ' + $(if ($NestedPromptLevel -ge 1) { '>>' }) + ' > ' } } diff --git a/Source/BookGen.Shell.Shared/packages.lock.json b/Source/BookGen.Shell.Shared/packages.lock.json index 012f2179..e18b60dc 100644 --- a/Source/BookGen.Shell.Shared/packages.lock.json +++ b/Source/BookGen.Shell.Shared/packages.lock.json @@ -4,11 +4,11 @@ "net10.0": { "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Webmaster442.WindowsTerminal": { @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" } } } diff --git a/Source/BookGen.Shellprog/packages.lock.json b/Source/BookGen.Shellprog/packages.lock.json index 5cdb23de..0a12cd5d 100644 --- a/Source/BookGen.Shellprog/packages.lock.json +++ b/Source/BookGen.Shellprog/packages.lock.json @@ -4,26 +4,26 @@ "net10.0": { "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "38Q8sEHwQ/+wVO/mwQBa0fcdHbezFpusHE+vBw/dSr6Fq/kzZm3H/NQX511Jki/R3FHd64IY559gdlHZQtYeEA==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "7sRvbBH3icaV9qil8fyBKmR+yEZ0yDU6Bq/KgBwswS36164yGaxbf7Kd4hD1iHZ2GfvyoJWWqBUBm9QX/IASAQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging.Configuration": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Configuration": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Spectre.Console": { @@ -40,85 +40,85 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==", + "resolved": "10.0.3", + "contentHash": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==", + "resolved": "10.0.3", + "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==", + "resolved": "10.0.3", + "contentHash": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", + "resolved": "10.0.3", + "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Zg8LLnfZs5o2RCHD/+9NfDtJ40swauemwCa7sI8gQoAye/UJHRZNpCtC7a5XE7l9Z7mdI8iMWnLZ6m7Q6S3jLg==", + "resolved": "10.0.3", + "contentHash": "PBlaoYeusaxNYyN4WFjzcXWlUDSvLUPxy/e6oP1SONOOYA/oBWT2uBmFGJMV9VTtXiXXxCB39LqlYWbsWE4UKA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "resolved": "10.0.3", + "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==", + "resolved": "10.0.3", + "contentHash": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + "resolved": "10.0.3", + "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" }, "bookgen.cli": { "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.3, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )" } }, "bookgen.contents": { @@ -127,7 +127,7 @@ "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -136,17 +136,17 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Webmaster442.WindowsTerminal": { diff --git a/Source/BookGen.Vfs/Extensions.cs b/Source/BookGen.Vfs/Extensions.cs index f9ff6546..f5b767af 100644 --- a/Source/BookGen.Vfs/Extensions.cs +++ b/Source/BookGen.Vfs/Extensions.cs @@ -3,6 +3,7 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Schema; @@ -12,60 +13,86 @@ namespace BookGen.Vfs; public static class Extensions { - public static XmlWriter CreateXmlWriter(this IWritableFileSystem fs, string path) + extension(IReadOnlyFileSystem fs) { - return XmlWriter.Create(fs.CreateTextWriter(path)); - } - - public static async Task DeserializeAsync(this IReadOnlyFileSystem fs, string path) - { - await using var stream = fs.OpenReadStream(path); - T? result = await JsonSerializer.DeserializeAsync(stream, JsonOptions.SerializerOptions); - return result; - } - - public static async Task SerializeAsync(this IWritableFileSystem fs, string path, T value, bool writeSchema) - { - await using var stream = fs.CreateWriteStream(path); - await JsonSerializer.SerializeAsync(stream, value, JsonOptions.SerializerOptions); - if (writeSchema) + public async Task DeserializeAsync(string path) { - var newName = Path.ChangeExtension(path, ".schema.json"); - await fs.WriteSchema(newName); + await using var stream = fs.OpenReadStream(path); + T? result = await JsonSerializer.DeserializeAsync(stream, JsonOptions.SerializerOptions); + return result; } - } - public static async Task WriteJsonAsync(this IWritableFileSystem fs, string path, JsonObject json) - { - await fs.WriteAllTextAsync(path, json.ToJsonString(JsonOptions.SerializerOptions)); - } + public async Task ReadJsonAsync(string path) + { + string content = await fs.ReadAllTextAsync(path); + JsonNode? parsed = JsonNode.Parse(content); + if (parsed is not JsonObject jsonObject) + { + throw new InvalidOperationException($"Failed to parse JSON from {path}"); + } + return jsonObject; + } - public static async Task ReadJsonAsync(this IReadOnlyFileSystem fs, string path) - { - string content = await fs.ReadAllTextAsync(path); - var parsed = JsonObject.Parse(content); - if (parsed is not JsonObject jsonObject) + public string GetFileNameInTargetFolder(IReadOnlyFileSystem targetFolder, string file, string newExtension) { - throw new InvalidOperationException($"Failed to parse JSON from {path}"); + ArgumentException.ThrowIfNullOrWhiteSpace(fs.Scope); + ArgumentException.ThrowIfNullOrWhiteSpace(targetFolder.Scope); + + var fullPath = Path.GetFullPath(file, fs.Scope); + + var relativePart = Path.GetRelativePath(fs.Scope, fullPath); + + return Path.ChangeExtension(Path.GetFullPath(relativePart, targetFolder.Scope), newExtension); } - return jsonObject; - } - public static async Task WriteSchema(this IWritableFileSystem fs, string path) - { - var node = JsonOptions.SerializerOptions.GetJsonSchemaAsNode(typeof(T), JsonOptions.ExporterOptions); - await fs.WriteAllTextAsync(path, node.ToJsonString(JsonOptions.SerializerOptions)); + public (string content, DateTime lastmodified) ReadInputFiles(string[] inputFiles) + { + StringBuilder md = new(inputFiles.Length * 1024); + DateTime lastmodified = DateTime.MinValue; + foreach (var inputFile in inputFiles) + { + string content = fs.ReadAllText(inputFile); + DateTime date = fs.GetLastModifiedUtc(inputFile); + + if (date > lastmodified) + lastmodified = date; + + md.Append(content); + + if (!content.EndsWith('\n')) + md.Append(System.Environment.NewLine); + } + return (md.ToString(), lastmodified); + } } - public static string GetFileNameInTargetFolder(this IReadOnlyFileSystem sourceFolder, IReadOnlyFileSystem targetFolder, string file, string newExtension) + extension(IWritableFileSystem fs) { - ArgumentException.ThrowIfNullOrWhiteSpace(sourceFolder.Scope); - ArgumentException.ThrowIfNullOrWhiteSpace(targetFolder.Scope); + public XmlWriter CreateXmlWriter(string path) + { + return XmlWriter.Create(fs.CreateTextWriter(path)); + } - var fullPath = Path.GetFullPath(file, sourceFolder.Scope); + public async Task WriteJsonAsync(string path, JsonObject json) + { + await fs.WriteAllTextAsync(path, json.ToJsonString(JsonOptions.SerializerOptions)); + } - var relativePart = Path.GetRelativePath(sourceFolder.Scope, fullPath); + public async Task WriteSchema(string path) + { + var node = JsonOptions.SerializerOptions.GetJsonSchemaAsNode(typeof(T), JsonOptions.ExporterOptions); + await fs.WriteAllTextAsync(path, node.ToJsonString(JsonOptions.SerializerOptions)); + } - return Path.ChangeExtension(Path.GetFullPath(relativePart, targetFolder.Scope), newExtension); + public async Task SerializeAsync(string path, T value, bool writeSchema) + { + await using var stream = fs.CreateWriteStream(path); + await JsonSerializer.SerializeAsync(stream, value, JsonOptions.SerializerOptions); + if (writeSchema) + { + var newName = Path.ChangeExtension(path, ".schema.json"); + await fs.WriteSchema(newName); + } + } } } diff --git a/Source/BookGen.Vfs/JsonOptions.cs b/Source/BookGen.Vfs/JsonOptions.cs index 9c0618d9..d6ae0798 100644 --- a/Source/BookGen.Vfs/JsonOptions.cs +++ b/Source/BookGen.Vfs/JsonOptions.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -13,7 +13,7 @@ namespace BookGen.Vfs; -internal static class JsonOptions +public static class JsonOptions { public readonly static JsonSerializerOptions SerializerOptions = new(JsonSerializerOptions.Default) { diff --git a/Source/BookGen/BookGenArgumentBase.cs b/Source/BookGen/BookGenArgumentBase.cs index 83126586..0c902746 100644 --- a/Source/BookGen/BookGenArgumentBase.cs +++ b/Source/BookGen/BookGenArgumentBase.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -17,8 +17,27 @@ public class BookGenArgumentBase : ArgumentsBase, IVerbosablityToggle [Switch("d", "dir")] public string Directory { get; set; } + [Switch("co", "configoverlay")] + public string ConfigOverlay { get; set; } = string.Empty; + public BookGenArgumentBase() { Directory = Environment.CurrentDirectory; } + + override public ValidationResult Validate(IValidationContext context) + { + if (!context.FileSystem.DirectoryExists(Directory)) + { + return ValidationResult.Error($"Directory '{Directory}' does not exist."); + } + + if (!string.IsNullOrEmpty(ConfigOverlay) + && !context.FileSystem.FileExists(ConfigOverlay)) + { + return ValidationResult.Error($"Config overlay file '{ConfigOverlay}' does not exist."); + } + + return ValidationResult.Ok(); + } } diff --git a/Source/BookGen/BuildCommandBase.cs b/Source/BookGen/BuildCommandBase.cs index b4e798f2..c0f4e1d8 100644 --- a/Source/BookGen/BuildCommandBase.cs +++ b/Source/BookGen/BuildCommandBase.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -47,7 +47,7 @@ public override async Task ExecuteAsync(BuildArguments arguments, IReadOnly _target.Scope = arguments.OutputDirectory; using var env = new BookEnvironment(_soruce, _target, _assetSource); - EnvironmentStatus status = await env.Initialize(); + EnvironmentStatus status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Commands/HelpCommand.cs b/Source/BookGen/Commands/HelpCommand.cs index 4f28c9fe..bc52e1c0 100644 --- a/Source/BookGen/Commands/HelpCommand.cs +++ b/Source/BookGen/Commands/HelpCommand.cs @@ -16,6 +16,7 @@ internal sealed class HelpCommand : Command { private readonly IHelpProvider _helpProvider; private readonly HashSet _commandNames; + private readonly HelpRenderer _renderer = new(); public HelpCommand(IHelpProvider helpProvider, ICommandRunnerProxy runnerProxy) { @@ -27,7 +28,7 @@ public override int Execute(IReadOnlyList context) { if (context.Count == 0) { - HelpRenderer.RenderHelp(_helpProvider.GetCommandHelp("help")); + _renderer.RenderHelp(_helpProvider.GetCommandHelp("help")); return ExitCodes.Success; } @@ -38,7 +39,7 @@ public override int Execute(IReadOnlyList context) return ExitCodes.GeneralError; } - HelpRenderer.RenderHelp(_helpProvider.GetCommandHelp(command)); + _renderer.RenderHelp(_helpProvider.GetCommandHelp(command)); return ExitCodes.Success; } diff --git a/Source/BookGen/Commands/LinksCommand.cs b/Source/BookGen/Commands/LinksCommand.cs index d29212b2..19b84962 100644 --- a/Source/BookGen/Commands/LinksCommand.cs +++ b/Source/BookGen/Commands/LinksCommand.cs @@ -1,8 +1,10 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- +using System.Collections.Concurrent; +using System.Net; using System.Text.RegularExpressions; using Bookgen.Lib; @@ -17,8 +19,15 @@ namespace BookGen.Commands; [CommandName("links")] -internal sealed partial class LinksCommand : AsyncCommand +internal sealed partial class LinksCommand : AsyncCommand { + public sealed class LinkArguments : BookGenArgumentBase + { + [Switch("vf", "verify")] + public bool Verify { get; set; } + } + + private readonly IWritableFileSystem _soruce; private readonly ILogger _logger; @@ -28,12 +37,12 @@ public LinksCommand(IWritableFileSystem soruce, ILogger logger) _logger = logger; } - public override async Task ExecuteAsync(BookGenArgumentBase arguments, IReadOnlyList context) + public override async Task ExecuteAsync(LinkArguments arguments, IReadOnlyList context) { _soruce.Scope = arguments.Directory; using var env = new BookEnvironment(_soruce, _soruce); - EnvironmentStatus status = await env.Initialize(); + EnvironmentStatus status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { @@ -42,6 +51,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea } Dictionary allLinks = new(); + Dictionary badLinks = new(); foreach (var chapter in env.TableOfContents.Chapters) { @@ -54,27 +64,49 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea var text = await _soruce.ReadAllTextAsync(file); chapterLinks.UnionWith(GetLinks(text)); } + + if (arguments.Verify) + { + _logger.LogInformation("Verifying links in {chapter}...", chapter.Title); + ConcurrentDictionary linksWithIssues = await VerifyLinks(chapterLinks); + if (linksWithIssues.Count > 0) + { + _logger.LogWarning("Found {count} links with issues in {chapter}:", linksWithIssues.Count, chapter.Title); + badLinks.Add(chapter.Title, linksWithIssues.Select(kvp => $"{kvp.Key} - {kvp.Value}").ToArray()); + } + } + allLinks.Add(chapter.Title, chapterLinks.ToArray()); chapterLinks.Clear(); } + await WriteMarkdown(allLinks, "links.md"); + + if (arguments.Verify) + { + await WriteMarkdown(badLinks, "links.issues.md"); + } + + return ExitCodes.Success; + } + + private async Task WriteMarkdown(Dictionary dataSet, string fileName) + { MarkdownBuilder markdown = new(); - foreach (var linkData in allLinks) + foreach (var linkData in dataSet) { markdown.Heading(2, linkData.Key); markdown.UnorderedList(linkData.Value); } - _logger.LogInformation("Writing links.md..."); - await _soruce.WriteAllTextAsync("links.md", markdown.ToString()); - - return ExitCodes.Success; + _logger.LogInformation("Writing {file}...", fileName); + await _soruce.WriteAllTextAsync(fileName, markdown.ToString()); } [GeneratedRegex(@"https?://[^\s\)\]\}""'<>]+")] - private partial Regex Links { get; } + private static partial Regex Links { get; } - private IEnumerable GetLinks(string text) + private static IEnumerable GetLinks(string text) { var links = Links.Matches(text); foreach (Match link in links) @@ -82,4 +114,67 @@ private IEnumerable GetLinks(string text) yield return link.Value; } } + + private async Task> VerifyLinks(HashSet chapterLinks) + { + ConcurrentDictionary linksWithIssues = new(); + + await Parallel.ForEachAsync(chapterLinks, async (link, cancellationToken) => + { + using var client = CreateHttpClient(); + + try + { + _logger.LogDebug("Verifying {link}...", link); + using var request = new HttpRequestMessage(HttpMethod.Head, link); + + using var response = await client.SendAsync(request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); + + // If HEAD is not allowed, fall back to a minimal GET + if (response.StatusCode == HttpStatusCode.MethodNotAllowed || + response.StatusCode == HttpStatusCode.NotImplemented) + { + using var getRequest = new HttpRequestMessage(HttpMethod.Get, link); + using var getResponse = await client.SendAsync(getRequest, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken); + + if (!getResponse.IsSuccessStatusCode) + { + linksWithIssues.TryAdd(link, getResponse.StatusCode); + } + + return; + } + + if (!response.IsSuccessStatusCode) + { + linksWithIssues.TryAdd(link, response.StatusCode); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error verifying link: {link}", link); + linksWithIssues.TryAdd(link, HttpStatusCode.ServiceUnavailable); + } + }); + + return linksWithIssues; + } + + private static HttpClient CreateHttpClient() + { + var client = new HttpClient + { + Timeout = TimeSpan.FromSeconds(5), + }; + + client.DefaultRequestHeaders.UserAgent.Add( + new System.Net.Http.Headers.ProductInfoHeaderValue("BookGenLinkChecker", "1.0")); + + return client; + } + } diff --git a/Source/BookGen/Commands/Md2HtmlCommand.cs b/Source/BookGen/Commands/Md2HtmlCommand.cs index 4415f93a..f5e2355f 100644 --- a/Source/BookGen/Commands/Md2HtmlCommand.cs +++ b/Source/BookGen/Commands/Md2HtmlCommand.cs @@ -71,7 +71,7 @@ public override ValidationResult Validate(IValidationContext context) if (string.IsNullOrEmpty(OutputFile)) result.AddIssue("Output file must be specified"); - if (!InputFiles.Any()) + if (InputFiles.Length == 0) result.AddIssue("An Input file must be specified"); foreach (var inputfile in InputFiles) @@ -105,7 +105,7 @@ public Md2HtmlCommand(ILogger log, IWritableFileSystem fileSystem, IAssetSource public override int Execute(Md2HtmlArguments arguments, IReadOnlyList context) { - (string md, DateTime lastmodified) = ReadInputFiles(arguments.InputFiles); + (string md, DateTime lastmodified) = _fileSystem.ReadInputFiles(arguments.InputFiles); string? pageTemplate = string.Empty; @@ -133,9 +133,9 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co PrismJsInterop = new PrismJsInterop(_assetSource) }; - using var mdToHtml = new MarkdownConverter(settings); + using var markdonwConverter = new MarkdownConverter(settings); - string? mdcontent = mdToHtml.RenderMarkdownToHtml(md); + string? mdcontent = markdonwConverter.RenderMarkdownToHtml(md); string rendered; if (arguments.RawHtml) @@ -163,26 +163,6 @@ public override int Execute(Md2HtmlArguments arguments, IReadOnlyList co return ExitCodes.Success; } - private (string content, DateTime lastmodified) ReadInputFiles(string[] inputFiles) - { - StringBuilder md = new(inputFiles.Length * 1024); - DateTime lastmodified = DateTime.MinValue; - foreach (var inputFile in inputFiles) - { - string content = _fileSystem.ReadAllText(inputFile); - DateTime date = _fileSystem.GetLastModifiedUtc(inputFile); - - if (date > lastmodified) - lastmodified = date; - - md.Append(content); - - if (!content.EndsWith('\n')) - md.Append(System.Environment.NewLine); - } - return (md.ToString(), lastmodified); - } - private bool ValidateTemplate(string pageTemplate) { bool returnValue = true; diff --git a/Source/BookGen/Commands/Md2TerminalCommand.cs b/Source/BookGen/Commands/Md2TerminalCommand.cs new file mode 100644 index 00000000..4c20eabd --- /dev/null +++ b/Source/BookGen/Commands/Md2TerminalCommand.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2025 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Text; + +using Bookgen.Lib.Domain.IO.Configuration; +using Bookgen.Lib.Markdown; + +using BookGen.Cli; +using BookGen.Cli.Annotations; +using BookGen.Vfs; + +using Microsoft.Extensions.Logging; + +namespace BookGen.Commands; + +[CommandName("md2terminal")] +internal sealed class Md2TerminalCommand : Command +{ + private readonly ILogger _log; + private readonly IWritableFileSystem _fileSystem; + + internal sealed class Arguments : ArgumentsBase + { + [Switch("i", "input")] + public string[] InputFiles { get; set; } + + [Switch("o", "output")] + public string OutputFile { get; set; } + + public Arguments() + { + InputFiles = []; + OutputFile = string.Empty; + } + + public override ValidationResult Validate(IValidationContext context) + { + ValidationResult result = new(); + + if (string.IsNullOrEmpty(OutputFile)) + result.AddIssue("Output file must be specified"); + + if (InputFiles.Length == 0) + result.AddIssue("An Input file must be specified"); + + foreach (var inputfile in InputFiles) + { + if (!context.FileSystem.FileExists(inputfile)) + result.AddIssue($"Input file: {inputfile} doesn't exist"); + } + + return base.Validate(context); + } + } + + public Md2TerminalCommand(ILogger log, IWritableFileSystem fileSystem) + { + _log = log; + _fileSystem = fileSystem; + } + + private static void WriteToStdout(string rendered) + { + Console.OutputEncoding = Encoding.UTF8; + Spectre.Console.AnsiConsole.WriteLine(rendered); + } + + public override int Execute(Arguments arguments, IReadOnlyList context) + { + (string md, _) = _fileSystem.ReadInputFiles(arguments.InputFiles); + + using var settings = new RenderSettings(null!) + { + DeleteFirstH1 = false, + AutoEmbedSupportedLinks = false, + CssClasses = new CssClasses(), + HostUrl = string.Empty, + PrismJsInterop = null, + }; + + using var markdonwConverter = new MarkdownConverter(settings); + + var rendered = markdonwConverter.RenderMarkdownToTerminal(md); + + if (arguments.OutputFile == "-") + WriteToStdout(rendered); + else + _fileSystem.WriteAllText(arguments.OutputFile, rendered); + + return ExitCodes.Success; + } +} diff --git a/Source/BookGen/Commands/StatsCommand.cs b/Source/BookGen/Commands/StatsCommand.cs index 9ed62e59..6060acc3 100644 --- a/Source/BookGen/Commands/StatsCommand.cs +++ b/Source/BookGen/Commands/StatsCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -35,7 +35,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea _soruce.Scope = arguments.Directory; using var env = new BookEnvironment(_soruce, _soruce); - var status = await env.Initialize(); + var status = await env.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Commands/ToolsCommand.cs b/Source/BookGen/Commands/ToolsCommand.cs index c4760aa5..5c64cdf7 100644 --- a/Source/BookGen/Commands/ToolsCommand.cs +++ b/Source/BookGen/Commands/ToolsCommand.cs @@ -36,6 +36,7 @@ public ToolsCommand(IApiClient apiClient, ILogger logger) new ChromaDownloader(apiClient, _memoryStreamManager, _logger), new CopyPartyDownloader(apiClient, _memoryStreamManager, _logger), new GithubDownloader(apiClient, _memoryStreamManager, _logger), + new GlowDownloader(apiClient, _memoryStreamManager, _logger), new MicrosoftEditToolDownloader(apiClient, _memoryStreamManager, _logger), new PandocTooldownloader(apiClient, _memoryStreamManager, _logger), ]; diff --git a/Source/BookGen/Commands/ValidateCommand.cs b/Source/BookGen/Commands/ValidateCommand.cs index 568a9918..5fd031c3 100644 --- a/Source/BookGen/Commands/ValidateCommand.cs +++ b/Source/BookGen/Commands/ValidateCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -31,7 +31,7 @@ public override async Task ExecuteAsync(BookGenArgumentBase arguments, IRea using var environment = new BookEnvironment(_writableFileSystem, _writableFileSystem); - EnvironmentStatus status = await environment.Initialize(); + EnvironmentStatus status = await environment.Initialize(arguments.ConfigOverlay); if (!status.IsOk) { diff --git a/Source/BookGen/Infrastructure/HelpRenderer.cs b/Source/BookGen/Infrastructure/HelpRenderer.cs index e64b9e0c..0ee95062 100644 --- a/Source/BookGen/Infrastructure/HelpRenderer.cs +++ b/Source/BookGen/Infrastructure/HelpRenderer.cs @@ -3,113 +3,37 @@ // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- -using Spectre.Console; +using Bookgen.Lib.Markdown.Renderers.Terminal; + +using Markdig; +using Markdig.Parsers; namespace BookGen.Infrastructure; -public static class HelpRenderer +internal sealed class HelpRenderer { - public static string[][] GetPages(IEnumerable article) - { - int pageSize = Console.WindowHeight - 3; - IReadOnlyList reWraped = DoReWrap(article, pageSize, Console.WindowWidth); - return reWraped.Chunk(pageSize).ToArray(); - } + private readonly MarkdownPipeline _terminalPipeLine; - public static void RenderPage(string[] pageContent) + public HelpRenderer() { - foreach (var line in pageContent) - { - if (line.StartsWith("# ")) - AnsiConsole.MarkupInterpolated($"[green bold]{line}[/]{Environment.NewLine}"); - else if (line.StartsWith('`') || line.EndsWith('`')) - AnsiConsole.MarkupInterpolated($"[aqua]{line}[/]{Environment.NewLine}"); - else - AnsiConsole.MarkupInterpolated($"[italic]{line}[/]{Environment.NewLine}"); - } - } - - public static void RenderHelp(IEnumerable article) - { - var pages = GetPages(article); - Console.Clear(); - - int currentPage = -1; - int nextPage = 0; - bool run = pages.Length > 1; - do - { - if (currentPage != nextPage) - { - currentPage = nextPage; - Console.Clear(); - RenderPage(pages[currentPage]); - RenderUsage(currentPage, pages.Length); - } - - if (!run) continue; - - var key = Console.ReadKey(); - switch (key.Key) - { - case ConsoleKey.LeftArrow: - case ConsoleKey.UpArrow: - nextPage = CalculatePage(currentPage, pages.Length, -1); - break; - case ConsoleKey.DownArrow: - case ConsoleKey.RightArrow: - nextPage = CalculatePage(currentPage, pages.Length, +1); - break; - case ConsoleKey.Escape: - case ConsoleKey.Q: - run = false; - Console.Clear(); - break; - } - } - while (run); + _terminalPipeLine = new MarkdownPipelineBuilder().Build(); } - private static void RenderUsage(int currentPage, int pages) + public void RenderHelp(IEnumerable article) { - if (pages < 2) - return; + string md = string.Join(Environment.NewLine, article); + var document = MarkdownParser.Parse(md, _terminalPipeLine); - AnsiConsole.WriteLine(); - AnsiConsole.MarkupInterpolated($"[teal]{currentPage + 1} of {pages}[/]"); - AnsiConsole.MarkupInterpolated($" [silver]ESC or Q: Exit, <- Prev, Next ->[/]{Environment.NewLine}"); - } + using var writer = new StringWriter(); + var renderer = new TerminalRenderer(writer, new RenderOptions()); - private static int CalculatePage(int currentPage, int pages, int offset) - { - int newIndex = currentPage + offset; + renderer.Render(document); + renderer.Writer.Flush(); - if (newIndex < 0) - newIndex = 0; + using var reader = new StringReader(writer.ToString()); - if (newIndex > pages - 1) - newIndex = pages - 1; + Webmaster442.WindowsTerminal.Wigets.Pager pager = new(reader); - return newIndex; - } - - private static List DoReWrap(IEnumerable article, int pageSize, int windowWidth) - { - List result = new(pageSize); - foreach (string line in article) - { - if (line.Length > windowWidth) - { - var newLines = line - .Chunk(windowWidth) - .Select(chrs => new string(chrs)); - result.AddRange(newLines); - } - else - { - result.Add(line); - } - } - return result; + pager.Show(false); } } diff --git a/Source/BookGen/Program.cs b/Source/BookGen/Program.cs index 28fcbb5e..f274c009 100644 --- a/Source/BookGen/Program.cs +++ b/Source/BookGen/Program.cs @@ -62,10 +62,11 @@ ExcptionExitCode = -1, PlatformNotSupportedExitCode = 4, EnableUtf8Output = true, -}); - -runner.ExceptionHandlerDelegate = OnException; -runner.BeforeRunHook = OnBeforeRun; +}) +{ + ExceptionHandlerDelegate = OnException, + BeforeRunHook = OnBeforeRun +}; runner .AddDefaultCommand() diff --git a/Source/BookGen/Properties/launchSettings.json b/Source/BookGen/Properties/launchSettings.json index d3340b37..ab444ce0 100644 --- a/Source/BookGen/Properties/launchSettings.json +++ b/Source/BookGen/Properties/launchSettings.json @@ -44,6 +44,15 @@ "Templates": { "commandName": "Project", "commandLineArgs": "templates" + }, + "Help": { + "commandName": "Project", + "commandLineArgs": "help" + }, + "Links --verify": { + "commandName": "Project", + "commandLineArgs": "links --verify", + "workingDirectory": "G:\\Konyv\\hellocsharp" } } } \ No newline at end of file diff --git a/Source/BookGen/Tooldownloaders/GlowDownloader.cs b/Source/BookGen/Tooldownloaders/GlowDownloader.cs new file mode 100644 index 00000000..985b11f1 --- /dev/null +++ b/Source/BookGen/Tooldownloaders/GlowDownloader.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2025 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Bookgen.Lib.Domain.Github; + +using BookGen.Infrastructure.Tools; +using BookGen.Vfs; + +using Microsoft.Extensions.Logging; +using Microsoft.IO; + +namespace BookGen.Tooldownloaders; + +internal sealed class GlowDownloader : TooldownloaderBase +{ + public GlowDownloader(IApiClient apiClient, + RecyclableMemoryStreamManager memoryStreamManager, + ILogger log) + : base(apiClient, memoryStreamManager, log) + { + } + + protected override ToolInfo CreateToolInfo() + { + return new ToolInfo + { + Name = "Glow", + ApproximateSize = "18 MiB", + RepoOwner = "charmbracelet", + RepoName = "glow", + FolderName = "glow", + }; + } + + protected override ReleaseAsset? GetReleaseAsset(IEnumerable releaseAssets) + { + return releaseAssets + .Where(r => r.Name.EndsWith("Windows_x86_64.zip")) + .OrderByDescending(r => r.CreatedAt) + .FirstOrDefault(); + } +} diff --git a/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs b/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs index b5f6908f..0bd06bd2 100644 --- a/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs +++ b/Source/BookGen/Tooldownloaders/ToolDownloadUi.cs @@ -11,7 +11,7 @@ namespace BookGen.Tooldownloaders; -internal class ToolDownloadUi : IDownloadUi +internal sealed class ToolDownloadUi : IDownloadUi { private class ExtendedProgresBar : Progressbar { diff --git a/Source/BookGen/packages.lock.json b/Source/BookGen/packages.lock.json index 1329cd17..92ab81e4 100644 --- a/Source/BookGen/packages.lock.json +++ b/Source/BookGen/packages.lock.json @@ -4,41 +4,41 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.44.0, )", - "resolved": "0.44.0", - "contentHash": "X+CYMjcUnh/yO24wOSQxVFLiGqWrrtXJ5M7toHiM1Zk4Fg9UMLN5fkaq6FSOWH+mIprsHHgDMlq3MJhmrXalhg==" + "requested": "[0.45.0, )", + "resolved": "0.45.0", + "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" }, "Microsoft.Extensions.Logging": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "9ItMpMLFZFJFqCuHLLbR3LiA4ahA8dMtYuXpXl2YamSDWZhYS9BruPprkftY0tYi2bQ0slNrixdFm+4kpz1g5w==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "YkmyiPIWAXVb+lPIrM0LE5bbtLOJkCiRTFiHpkVOvhI7uTvCfoOHLEN0LcsY56GpSD7NqX3gJNpsaDe87/B3zg==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Microsoft.Extensions.Logging.Console": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "38Q8sEHwQ/+wVO/mwQBa0fcdHbezFpusHE+vBw/dSr6Fq/kzZm3H/NQX511Jki/R3FHd64IY559gdlHZQtYeEA==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "7sRvbBH3icaV9qil8fyBKmR+yEZ0yDU6Bq/KgBwswS36164yGaxbf7Kd4hD1iHZ2GfvyoJWWqBUBm9QX/IASAQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging.Configuration": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging.Configuration": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3" } }, "Microsoft.IO.RecyclableMemoryStream": { @@ -113,78 +113,78 @@ }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "njoRekyMIK+smav8B6KL2YgIfUtlsRNuT7wvurpLW+m/hoRKVnoELk2YxnUnWRGScCd1rukLMxShwLqEOKowDg==", + "resolved": "10.0.3", + "contentHash": "H1Cjv2xmm7O3iAGmFTcnSM0ZhLQ/7SqefmAvSJoT1PbXoxeYc2fo0mCLn2JlVbr9E6YpoU9q/o0fI9neDJB0xQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "kPlU11hql+L9RjrN2N9/0GcRcRcZrNFlLLjadasFWeBORT6pL6OE+RYRk90GGCyVGSxTK+e1/f3dsMj5zpFFiQ==", + "resolved": "10.0.3", + "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Lp4CZIuTVXtlvkAnTq6QvMSW7+H62gX2cU2vdFxHQUxvrWTpi7LwYI3X+YAyIS0r12/p7gaosco7efIxL4yFNw==", + "resolved": "10.0.3", + "contentHash": "759UhpKaR5Jsll9kXpkft4z/7tpeF7Dw2rTY/9f9JchaSQTpRFNIPkZFZvoo7fFpbjUaqtDlO5aiGpmQrp/EUA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "zerXV0GAR9LCSXoSIApbWn+Dq1/T+6vbXMHGduq1LoVQRHT0BXsGQEau0jeLUBUcsoF/NaUT8ADPu8b+eNcIyg==", + "resolved": "10.0.3", + "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "Zg8LLnfZs5o2RCHD/+9NfDtJ40swauemwCa7sI8gQoAye/UJHRZNpCtC7a5XE7l9Z7mdI8iMWnLZ6m7Q6S3jLg==", + "resolved": "10.0.3", + "contentHash": "PBlaoYeusaxNYyN4WFjzcXWlUDSvLUPxy/e6oP1SONOOYA/oBWT2uBmFGJMV9VTtXiXXxCB39LqlYWbsWE4UKA==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.1", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Logging": "10.0.1", - "Microsoft.Extensions.Logging.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.1" + "Microsoft.Extensions.Configuration": "10.0.3", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Logging": "10.0.3", + "Microsoft.Extensions.Logging.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.3" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "G6VVwywpJI4XIobetGHwg7wDOYC2L2XBYdtskxLaKF/Ynb5QBwLl7Q//wxAR2aVCLkMpoQrjSP9VoORkyddsNQ==", + "resolved": "10.0.3", + "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "pL78/Im7O3WmxHzlKUsWTYchKL881udU7E26gCD3T0+/tPhWVfjPwMzfN/MRKU7aoFYcOiqcG2k1QTlH5woWow==", + "resolved": "10.0.3", + "contentHash": "bn6QoBbbvwmzLIFyxrnL2/e+sqoNUOGbHyfWK9DPONMv1mDCYHm/C7MusYASM31b2lUx6OiDmonb3v+dv5t0nA==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.1", - "Microsoft.Extensions.Configuration.Binder": "10.0.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1", - "Microsoft.Extensions.Options": "10.0.1", - "Microsoft.Extensions.Primitives": "10.0.1" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", + "Microsoft.Extensions.Configuration.Binder": "10.0.3", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", + "Microsoft.Extensions.Options": "10.0.3", + "Microsoft.Extensions.Primitives": "10.0.3" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.1", - "contentHash": "DO8XrJkp5x4PddDuc/CH37yDBCs9BYN6ijlKyR3vMb55BP1Vwh90vOX8bNfnKxr5B2qEI3D8bvbY1fFbDveDHQ==" + "resolved": "10.0.3", + "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" }, "Newtonsoft.Json": { "type": "Transitive", @@ -193,37 +193,37 @@ }, "ShimSkiaSharp": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "WyMf+0aj5IJcHkG/t89WApGFg756oXB30ehu9kYu9hjdfS0snkXHoAiTJZqVlqyLOvzC6eA+nOQ2hI84g//5Pg==" + "resolved": "3.4.1", + "contentHash": "ab3J5OGdwYLyXnom90OghS9NyRPH9dtsRtW5B3mL+F43YTlxido3nUtiR9NjB/sI3jvG/J744C29bsSBQ28AfQ==" }, "SkiaSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "3.119.1", - "contentHash": "6hR3BdLhApjDxR1bFrJ7/lMydPfI01s3K+3WjIXFUlfC0MFCFCwRzv+JtzIkW9bDXs7XUVQS+6EVf0uzCasnGQ==" + "resolved": "3.119.2", + "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" }, "Svg.Custom": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "Wu0+Il+SZfY5+AT25b2PMlSbBrzGIo/6vMMMdOK8jd25I9tdJJbxOFJNG87gFgo9UY9OmpIFzVobGOHzFdJYeQ==", + "resolved": "3.4.1", + "contentHash": "ksHa7Zv8X1InxSWAeM0sSSvZM2W6FWgneXjU0bdYrQ20Q6q0nl3g0uaSWe53i/CC1OmR+/JAxsp8E3/wigtcng==", "dependencies": { "ExCSS": "4.3.1" } }, "Svg.Model": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "N4yWlvqv1XWZ+VvI29lT4HkEgtiP3rS/poMfWiVzz6Ao/3IjQCfmo2qrKphq77IqTdMYyh7wGDknMId7YrQY5Q==", + "resolved": "3.4.1", + "contentHash": "Zn04CWWIwV+qlJ4x4vg8FdCYNew5dRcsvkcCpzZggFKHGExIn4WTBtJMuLH8htuiLnbbgF6woIYpUll+seHfjw==", "dependencies": { - "ShimSkiaSharp": "3.2.1", - "Svg.Custom": "3.2.1" + "ShimSkiaSharp": "3.4.1", + "Svg.Custom": "3.4.1" } }, "bookgen.cli": { "type": "Project", "dependencies": { "BookGen.Vfs": "[1.0.0, )", - "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.1, )", - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )" + "Microsoft.Extensions.DependencyInjection.Abstractions": "[10.0.3, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )" } }, "bookgen.contents": { @@ -234,22 +234,22 @@ "dependencies": { "BookGen.Shell.Shared": "[1.0.0, )", "BookGen.Vfs": "[1.0.0, )", - "Markdig": "[0.44.0, )", + "Markdig": "[0.45.0, )", "Microsoft.ClearScript": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.linux-x64": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.win-x64": "[7.5.0, )", - "SkiaSharp": "[3.119.1, )", - "SkiaSharp.NativeAssets.Linux": "[3.119.1, )", - "SkiaSharp.NativeAssets.Win32": "[3.119.1, )", - "Svg.Skia": "[3.2.1, )", - "System.ServiceModel.Syndication": "[10.0.1, )", + "SkiaSharp": "[3.119.2, )", + "SkiaSharp.NativeAssets.Linux": "[3.119.2, )", + "SkiaSharp.NativeAssets.Win32": "[3.119.2, )", + "Svg.Skia": "[3.4.1, )", + "System.ServiceModel.Syndication": "[10.0.3, )", "YamlDotNet": "[16.3.0, )" } }, "bookgen.shell.shared": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "[10.0.1, )", + "Microsoft.Extensions.Logging.Abstractions": "[10.0.3, )", "Webmaster442.WindowsTerminal": "[4.1.1, )" } }, @@ -283,48 +283,48 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "oIy8fQxxbUsSrrOvgBqlVgOeCtDmrcynnTG+FQufcUWBrwyPfwlUkCDB2vaiBeYPyT+20u9/HeuHeBf+H4F/8g==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" }, "SkiaSharp": { "type": "CentralTransitive", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "+Ru1BTSZQne3Vp+vbSb50Ke3Nlc3ZnItxx4+751J9WZ8YzLKAV/n+9DAo4zFTyeCI//ueT63c+VybmTTpYBEiw==", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "nmy2dOFWPvQKMglfpjz8+/xQQcSrL9jzul3cUyzCJVSwrmSAw+6B1sEgU7jt6NZBptwGq2k/V0kjyu2GizMFtg==", "dependencies": { - "SkiaSharp.NativeAssets.Win32": "3.119.1", - "SkiaSharp.NativeAssets.macOS": "3.119.1" + "SkiaSharp.NativeAssets.Win32": "3.119.2", + "SkiaSharp.NativeAssets.macOS": "3.119.2" } }, "SkiaSharp.NativeAssets.Linux": { "type": "CentralTransitive", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "9YNoc4SeKvQhrwiqwT4ezkNfMywPdPSK+UFvo/CaoXqLixcnYOTsQKm5BF9mc4+q3vKgDtEgMt0d1ygZhJTEHg==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" }, "SkiaSharp.NativeAssets.Win32": { "type": "CentralTransitive", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "8C4GSXVJqSr0y3Tyyv5jz6MJSTVUyYkMjeKrzK+VyZPGLo89MNoUEclVuYahzOCDdtbfXrd2HtxXfDuvoSXrUw==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" }, "Svg.Skia": { "type": "CentralTransitive", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "pRJtMOc2hTcnxSu7cx6lf806S+2VrQ0eqUpGMBUQQ/pIIvzrtn0+lUjdABTvjr5rsdQF9OxE+r9npq2+3c6T4A==", + "requested": "[3.4.1, )", + "resolved": "3.4.1", + "contentHash": "fNGHIeIUtEDo41P4MYfVqjNL902t8EP5/tBY9vYDg8VbKSOD84ZlBfCL4KSxjf//HcJi1Uz4uZCUXuEAKJirCw==", "dependencies": { "SkiaSharp": "2.88.9", - "Svg.Custom": "3.2.1", - "Svg.Model": "3.2.1" + "Svg.Custom": "3.4.1", + "Svg.Model": "3.4.1" } }, "System.ServiceModel.Syndication": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "Z3+s66hOp7JVDOhkVk0gH/sjsshn99NUMiILcXLaA+3qe2OkLr3nB+3bRzXdfF6arlJBxGfkkki9GP6R9kvdFg==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "mloJBxfbjYXgfcfMvH40UWwzekATlUzHLMKPQpg4qab+gpHRgQkhgo4DLz3v7Kacn6000sqoNkxiyk0pJ5x/ig==" }, "Webmaster442.WindowsTerminal": { "type": "CentralTransitive", diff --git a/Source/Bookgen.Lib/BookEnvironment.cs b/Source/Bookgen.Lib/BookEnvironment.cs index 96f99460..0973e370 100644 --- a/Source/Bookgen.Lib/BookEnvironment.cs +++ b/Source/Bookgen.Lib/BookEnvironment.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2019-2025 Ruzsinszki Gábor +// (c) 2019-2026 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -57,7 +57,7 @@ public void Dispose() } } - public async Task Initialize() + public async Task Initialize(string configOverlay) { if (_isInitialized) { @@ -87,7 +87,17 @@ public async Task Initialize() return status; } - Config? config = await _source.DeserializeAsync(FileNameConstants.ConfigFile); + var baseConfig = await _source.ReadJsonAsync(FileNameConstants.ConfigFile); + + JsonMerger configMerger = new JsonMerger(baseConfig); + + if (!string.IsNullOrEmpty(configOverlay)) + { + var overlayConfig = await _source.ReadJsonAsync(configOverlay); + configMerger.Merge(overlayConfig); + } + + Config? config = configMerger.Deserialize(); if (config == null) { diff --git a/Source/Bookgen.Lib/Bookgen.Lib.csproj b/Source/Bookgen.Lib/Bookgen.Lib.csproj index e8c9dd53..5b8ae154 100644 --- a/Source/Bookgen.Lib/Bookgen.Lib.csproj +++ b/Source/Bookgen.Lib/Bookgen.Lib.csproj @@ -25,7 +25,7 @@ - + diff --git a/Source/Bookgen.Lib/Confighandling/JsonMerger.cs b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs new file mode 100644 index 00000000..e8827c8d --- /dev/null +++ b/Source/Bookgen.Lib/Confighandling/JsonMerger.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using System.Text.Json.Nodes; + +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using BookGen.Vfs; + +namespace Bookgen.Lib.Confighandling; + +internal sealed class JsonMerger +{ + private JsonObject _baseObject; + + public JsonMerger(JsonObject baseObject) + { + _baseObject = baseObject; + } + + private static JsonNode? Merge(JsonNode jsonBase, + JsonNode jsonMerge, + bool mergeIfAlreadyExists = true) + { + if (jsonBase == null || jsonMerge == null) + return jsonBase; + + switch (jsonBase) + { + case JsonObject jsonBaseObj when jsonMerge is JsonObject jsonMergeObj: + { + //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array so the node can then be + // re-assigned to the target/base Json; clearing the Object seems to be the most efficient approach... + var mergeNodesArray = jsonMergeObj.ToArray(); + jsonMergeObj.Clear(); + + foreach (var prop in mergeNodesArray) + { + if (mergeIfAlreadyExists || !jsonBaseObj.ContainsKey(prop.Key)) + jsonBaseObj[prop.Key] = jsonBaseObj[prop.Key] switch + { + JsonObject jsonBaseChildObj when prop.Value is JsonObject jsonMergeChildObj + => Merge(jsonBaseChildObj, jsonMergeChildObj), + JsonArray jsonBaseChildArray when prop.Value is JsonArray jsonMergeChildArray + => Merge(jsonBaseChildArray, jsonMergeChildArray), + _ => prop.Value + }; + } + break; + } + case JsonArray jsonBaseArray when jsonMerge is JsonArray jsonMergeArray: + { + //NOTE: We must materialize the set (e.g. to an Array), and then clear the merge array, + // so they can then be re-assigned to the target/base Json... + var mergeNodesArray = jsonMergeArray.ToArray(); + jsonMergeArray.Clear(); + foreach (var mergeNode in mergeNodesArray) jsonBaseArray.Add(mergeNode); + break; + } + default: + throw new ArgumentException($"The JsonNode type [{jsonBase.GetType().Name}] is incompatible for merging with the target/base " + + $"type [{jsonMerge.GetType().Name}]; merge requires the types to be the same."); + + } + + return jsonBase; + } + + + public void Merge(JsonObject overlay) + { + var result = Merge(_baseObject, overlay); + if (result is JsonObject mergedObj) + { + _baseObject = mergedObj; + return; + } + throw new InvalidOperationException("Merging resulted in invalid object"); + } + + public T? Deserialize() + => JsonSerializer.Deserialize(_baseObject, JsonOptions.SerializerOptions); +} diff --git a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs index c979d9a2..7e42a716 100644 --- a/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs +++ b/Source/Bookgen.Lib/Markdown/MarkdownConverter.cs @@ -5,11 +5,9 @@ using Bookgen.Lib.Markdown.Renderers.Terminal; using Bookgen.Lib.Markdown.TableOfContents; -using Bookgen.Lib.Pipeline; using Markdig; - -using Microsoft.AspNetCore.Components; +using Markdig.Parsers; namespace Bookgen.Lib.Markdown; @@ -39,6 +37,7 @@ public MarkdownConverter(RenderSettings settings) _terminalPipeLine = new MarkdownPipelineBuilder() .UseYamlFrontMatter() + .UseAutoLinks() .Build(); } @@ -56,13 +55,19 @@ public void Dispose() public string RenderMarkdownToHtml(string markdown) => Markdig.Markdown.ToHtml(markdown, _htmlPipeLine); - public string RenderMarkdownToTerminal(string markdown) + public string RenderMarkdownToTerminal(string markdown, RenderOptions? renderOptions = null) { - PSMarkdownOptionInfo optionInfo = new(); + var document = MarkdownParser.Parse(markdown, _terminalPipeLine); using var writer = new StringWriter(); - var renderer = new VT100Renderer(writer, optionInfo); - return Markdig.Markdown.Convert(markdown, renderer, _terminalPipeLine).ToString() ?? ""; + renderOptions ??= new RenderOptions(); + + TerminalRenderer renderer = new TerminalRenderer(writer, renderOptions); + + renderer.Render(document); + renderer.Writer.Flush(); + + return renderer.Writer.ToString() ?? string.Empty; } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs new file mode 100644 index 00000000..cae7de68 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/AutolinkInlineRenderer.cs @@ -0,0 +1,25 @@ +using System.Web; + +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class AutolinkInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, AutolinkInline obj) + { + string url = obj.IsEmail + ? HttpUtility.UrlEncode($"mailto:{obj.Url}") + : HttpUtility.UrlEncode(obj.Url); + + var text = renderer + .Builder + .New() + .WithForegroundColor(renderer.RenderOptions.LinkColor) + .AppendLink(url, obj.Url) + .ResetFormat() + .ToString(); + + renderer.Write(text); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs new file mode 100644 index 00000000..87559381 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeBlockRenderer.cs @@ -0,0 +1,40 @@ +using Markdig.Helpers; +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class CodeBlockRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, CodeBlock obj) + { + if (obj?.Lines.Lines != null) + { + string begin = renderer.Builder + .New() + .WithBackgroundColor(renderer.RenderOptions.CodeBlockBackground) + .WithForegroundColor(renderer.RenderOptions.CodeBlockColor) + .ToString(); + + renderer.Write(begin); + + for (int i = 0; i < obj.Lines.Count; i++) + { + StringLine codeLine = obj.Lines.Lines[i]; + if (!string.IsNullOrWhiteSpace(codeLine.ToString())) + { + if (i == obj.Lines.Count - 1) + { + renderer.Write(codeLine.ToString()).WriteReset().WriteLine(); + } + else + { + renderer.WriteLine(codeLine.ToString()); + } + + } + } + + renderer.WriteLine(); + } + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs index 168158ce..aa9658bd 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/CodeInlineRenderer.cs @@ -1,17 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for inline code elements. -/// -internal class CodeInlineRenderer : VT100ObjectRenderer +internal sealed class CodeInlineRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, CodeInline obj) + protected override void Write(TerminalRenderer renderer, CodeInline obj) { - renderer.Write(renderer.EscapeSequences.FormatCode(obj.Content, isInline: true)); + var begin = renderer.Builder + .New() + .WithForegroundColor(renderer.RenderOptions.CodeInlineColor) + .WithItalic() + .ToString(); + + renderer.Write(begin); + + renderer.Write(obj.ContentSpan); + + renderer.WriteReset(); } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs new file mode 100644 index 00000000..713c6776 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/DelimiterInlineRenderer.cs @@ -0,0 +1,12 @@ +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class DelimiterInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, DelimiterInline obj) + { + renderer.Write(obj.ToLiteral()); + renderer.WriteChildren(obj); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs index 8e585e71..842fe39e 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/EmphasisInlineRenderer.cs @@ -1,17 +1,45 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +using System.Diagnostics; using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for bold and italics elements. -/// -internal class EmphasisInlineRenderer : VT100ObjectRenderer +internal sealed class EmphasisInlineRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, EmphasisInline obj) + private enum RenderAs { - renderer.Write(renderer.EscapeSequences.FormatEmphasis(obj.FirstChild?.ToString() ?? "", isBold: obj.DelimiterCount == 2)); + Regular = 0, + Bold, + Italic, + } + + private static RenderAs GetRenderOption(EmphasisInline obj) + { + if (obj.DelimiterChar is '*' or '_') + { + Debug.Assert(obj.DelimiterCount <= 2); + return obj.DelimiterCount == 2 ? RenderAs.Bold : RenderAs.Italic; + } + return RenderAs.Regular; + } + + protected override void Write(TerminalRenderer renderer, EmphasisInline obj) + { + RenderAs option = GetRenderOption(obj); + if (option == RenderAs.Regular) + { + renderer.WriteChildren(obj); + return; + } + + var preformat = renderer.Builder.New(); + + if (option == RenderAs.Bold) + preformat.WithBold(); + else + preformat.WithItalic(); + + renderer.Write(preformat.ToString()).WriteChildren(obj); + renderer.WriteReset(); } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs deleted file mode 100644 index b41df71a..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/FencedCodeBlockRenderer.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Helpers; -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for code blocks with language type. -/// -internal class FencedCodeBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, FencedCodeBlock obj) - { - if (obj?.Lines.Lines != null) - { - foreach (StringLine codeLine in obj.Lines.Lines) - { - if (!string.IsNullOrWhiteSpace(codeLine.ToString())) - { - // If the code block is of type YAML, then tab to right to improve readability. - // This specifically helps for parameters help content. - if (string.Equals(obj.Info, "yaml", StringComparison.OrdinalIgnoreCase)) - { - renderer.Write("\t").WriteLine(codeLine.ToString()); - } - else - { - renderer.WriteLine(renderer.EscapeSequences.FormatCode(codeLine.ToString(), isInline: false)); - } - } - } - - // Add a blank line after the code block for better readability. - renderer.WriteLine(); - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs deleted file mode 100644 index 342f2837..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeaderBlockRenderer.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for headings. -/// -internal class HeaderBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, HeadingBlock obj) - { - string? headerText = obj.Inline?.FirstChild?.ToString(); - - if (!string.IsNullOrEmpty(headerText)) - { - // Format header and then add blank line to improve readability. - switch (obj.Level) - { - case 1: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader1(headerText)); - renderer.WriteLine(); - break; - - case 2: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader2(headerText)); - renderer.WriteLine(); - break; - - case 3: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader3(headerText)); - renderer.WriteLine(); - break; - - case 4: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader4(headerText)); - renderer.WriteLine(); - break; - - case 5: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader5(headerText)); - renderer.WriteLine(); - break; - - case 6: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader6(headerText)); - renderer.WriteLine(); - break; - } - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs new file mode 100644 index 00000000..dd275d5b --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/HeadingRenderer.cs @@ -0,0 +1,27 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class HeadingRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, HeadingBlock obj) + { + string prefix = new string('#', obj.Level); + + var beginText = renderer + .Builder + .New() + .WithForegroundColor(renderer.RenderOptions.HeadingColor) + .WithBold() + .Append(prefix) + .Append(' ') + .ToString(); + + renderer + .Write(beginText) + .WriteLeafInline(obj) + .WriteReset() + .EnsureLine() + .WriteLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs deleted file mode 100644 index 44a3333d..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LeafInlineRenderer.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for leaf elements like plain text in paragraphs. -/// -internal class LeafInlineRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, LeafInline obj) - { - // If the next sibling is null, then this is the last line in the paragraph. - // Add new line character at the end. - // Else just write without newline at the end. - if (obj.NextSibling == null) - { - renderer.WriteLine(obj.ToString() ?? ""); - } - else - { - renderer.Write(obj.ToString()); - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs new file mode 100644 index 00000000..928779d1 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakInlineRenderer.cs @@ -0,0 +1,15 @@ +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class LineBreakInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, LineBreakInline obj) + { + if (obj.IsHard) + { + renderer.WriteLine(); + } + renderer.EnsureLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs deleted file mode 100644 index 2d7054b2..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LineBreakRenderer.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for line breaks. -/// -internal class LineBreakRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, LineBreakInline obj) - { - // If it is a hard line break add new line at the end. - // Else, add a space for after the last character to improve readability. - if (obj.IsHard) - { - renderer.WriteLine(); - } - else - { - renderer.Write(" "); - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs index a27491eb..9fc5dde2 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LinkInlineRenderer.cs @@ -1,27 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax.Inlines; +using Markdig.Syntax.Inlines; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for links. -/// -internal class LinkInlineRenderer : VT100ObjectRenderer +internal sealed class LinkInlineRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, LinkInline obj) + protected override void Write(TerminalRenderer renderer, LinkInline obj) { - string? text = obj.FirstChild?.ToString(); - - // Format link as image or link. if (obj.IsImage) { - renderer.Write(renderer.EscapeSequences.FormatImage(text)); + // TODO + return; } - else + + string? linkText = obj.FirstChild?.ToString(); + + if (obj.Url is null + || linkText is null) { - renderer.Write(renderer.EscapeSequences.FormatLink(text ?? "", obj.Url ?? "")); + return; } + + string text = renderer + .Builder + .New() + .WithForegroundColor(renderer.RenderOptions.LinkColor) + .AppendLink(obj.Url, linkText) + .ResetFormat() + .ToString(); + + renderer.Write(text); + } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs deleted file mode 100644 index b3a1f850..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListBlockRenderer.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for list blocks. -/// -internal class ListBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, ListBlock obj) - { - // start index of a numbered block. - int index = 1; - - foreach (var item in obj) - { - if (item is ListItemBlock listItem) - { - if (obj.IsOrdered) - { - RenderNumberedList(renderer, listItem, index++); - } - else - { - renderer.Write(listItem); - } - } - } - - renderer.WriteLine(); - } - - private static void RenderNumberedList(VT100Renderer renderer, ListItemBlock block, int index) - { - // For a numbered list, we need to make sure the index is incremented. - foreach (var line in block) - { - if (line is ParagraphBlock paragraphBlock && paragraphBlock.Inline != null) - { - renderer.Write(index.ToString()).Write(". ").Write(paragraphBlock.Inline); - } - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs deleted file mode 100644 index 7d4b60b0..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListItemBlockRenderer.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for items in a list block. -/// -internal class ListItemBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, ListItemBlock obj) - { - if (obj.Parent is ListBlock parent) - { - if (!parent.IsOrdered) - { - foreach (var line in obj) - { - RenderWithIndent(renderer, line, parent.BulletType, 0); - } - } - } - } - - private static void RenderWithIndent(VT100Renderer renderer, MarkdownObject block, char listBullet, int indentLevel) - { - // Indent left by 2 for each level on list. - string indent = Padding(indentLevel * 2); - - if (block is ParagraphBlock paragraphBlock && paragraphBlock.Inline != null) - { - renderer.Write(indent).Write(listBullet).Write(" ").Write(paragraphBlock.Inline); - } - else - { - // If there is a sublist, the block is a ListBlock instead of ParagraphBlock. - if (block is ListBlock subList) - { - foreach (var subListItem in subList) - { - if (subListItem is ListItemBlock subListItemBlock) - { - foreach (var line in subListItemBlock) - { - // Increment indent level for sub list. - RenderWithIndent(renderer, line, listBullet, indentLevel + 1); - } - } - } - } - } - } - - // Typical padding is at most a screen's width, any more than that and we won't bother caching. - private const int IndentCacheMax = 120; - - private static readonly string[] IndentCache = new string[IndentCacheMax]; - - internal static string Padding(int countOfSpaces) - { - if (countOfSpaces >= IndentCacheMax) - { - return new string(' ', countOfSpaces); - } - - var result = IndentCache[countOfSpaces]; - - if (result == null) - { - Interlocked.CompareExchange(ref IndentCache[countOfSpaces], new string(' ', countOfSpaces), comparand: null); - result = IndentCache[countOfSpaces]; - } - - return result; - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs new file mode 100644 index 00000000..0ae9bb82 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ListRenderer.cs @@ -0,0 +1,66 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class ListRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, ListBlock obj) + { + int indent = 0; + string sub = ""; + Write(renderer, obj, sub, ref indent); + } + + private static void Write(TerminalRenderer renderer, ListBlock obj, string sub, ref int indent) + { + const int indentSize = 2; + if (!obj.IsOrdered) + { + foreach (ListItemBlock item in obj.Cast()) + { + renderer.Write(new string(' ', indent * indentSize)); + renderer.Write("* "); + for (int i = 0; i < item.Count; i++) + { + Block subBlock = item[i]; + if (subBlock is ListBlock subListBlock) + { + indent++; + Write(renderer, subListBlock, "", ref indent); + } + else + { + renderer.Render(subBlock); + } + } + } + } + else + { + int number = 1; + foreach (ListItemBlock item in obj.Cast()) + { + renderer.Write(new string(' ', indent * indentSize)) + .Write(sub) + .Write(number.ToString()) + .Write(". "); + + for (int i = 0; i < item.Count; i++) + { + Block subBlock = item[i]; + if (subBlock is ListBlock subListBlock) + { + indent++; + var nsub = $"{sub} {number}."; + Write(renderer, subListBlock, nsub, ref indent); + } + else + { + renderer.Render(subBlock); + } + } + ++number; + } + } + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs new file mode 100644 index 00000000..56fadc82 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/LiteralInlineRenderer.cs @@ -0,0 +1,12 @@ +using Markdig.Syntax.Inlines; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class LiteralInlineRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, LiteralInline obj) + { + string content = obj.Content.ToString(); + renderer.Write(content); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs deleted file mode 100644 index 64406509..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/MarkdownOptionInfoProperty.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Enum to name all the properties of PSMarkdownOptionInfo. -/// -public enum MarkdownOptionInfoProperty -{ - /// - /// Property name Header1. - /// - Header1, - - /// - /// Property name Header2. - /// - Header2, - - /// - /// Property name Header3. - /// - Header3, - - /// - /// Property name Header4. - /// - Header4, - - /// - /// Property name Header5. - /// - Header5, - - /// - /// Property name Header6. - /// - Header6, - - /// - /// Property name Code. - /// - Code, - - /// - /// Property name Link. - /// - Link, - - /// - /// Property name Image. - /// - Image, - - /// - /// Property name EmphasisBold. - /// - EmphasisBold, - - /// - /// Property name EmphasisItalics. - /// - EmphasisItalics -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs deleted file mode 100644 index dbb230cc..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/PSMarkdownOptionInfo.cs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Class to represent color preference options for various Markdown elements. -/// -public sealed class PSMarkdownOptionInfo -{ - private const char Esc = (char)0x1b; - private const string EndSequence = "[0m"; - - /// - /// Gets or sets current VT100 escape sequence for header 1. - /// - public string Header1 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 2. - /// - public string Header2 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 3. - /// - public string Header3 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 4. - /// - public string Header4 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 5. - /// - public string Header5 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 6. - /// - public string Header6 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for code inline and code blocks. - /// - public string Code { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for links. - /// - public string Link { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for images. - /// - public string Image { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for bold text. - /// - public string EmphasisBold { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for italics text. - /// - public string EmphasisItalics { get; set; } - - /// - /// Gets or sets a value indicating whether VT100 escape sequences should be added. Default it true. - /// - public bool EnableVT100Encoding { get; set; } - - /// - /// Get the property as an rendered escape sequence. - /// This is used by formatting system for displaying. - /// - /// Name of the property to get as escape sequence. - /// Specified property name as escape sequence. - public string AsEscapeSequence(MarkdownOptionInfoProperty propertyName) - { - return propertyName switch - { - MarkdownOptionInfoProperty.Header1 => string.Concat(Esc, Header1, Header1, Esc, EndSequence), - MarkdownOptionInfoProperty.Header2 => string.Concat(Esc, Header2, Header2, Esc, EndSequence), - MarkdownOptionInfoProperty.Header3 => string.Concat(Esc, Header3, Header3, Esc, EndSequence), - MarkdownOptionInfoProperty.Header4 => string.Concat(Esc, Header4, Header4, Esc, EndSequence), - MarkdownOptionInfoProperty.Header5 => string.Concat(Esc, Header5, Header5, Esc, EndSequence), - MarkdownOptionInfoProperty.Header6 => string.Concat(Esc, Header6, Header6, Esc, EndSequence), - MarkdownOptionInfoProperty.Code => string.Concat(Esc, Code, Code, Esc, EndSequence), - MarkdownOptionInfoProperty.Link => string.Concat(Esc, Link, Link, Esc, EndSequence), - MarkdownOptionInfoProperty.Image => string.Concat(Esc, Image, Image, Esc, EndSequence), - MarkdownOptionInfoProperty.EmphasisBold => string.Concat(Esc, EmphasisBold, EmphasisBold, Esc, EndSequence), - MarkdownOptionInfoProperty.EmphasisItalics => string.Concat(Esc, EmphasisItalics, EmphasisItalics, Esc, EndSequence), - _ => throw new InvalidOperationException($"Unknown value: {propertyName}"), - }; - } - - /// - /// Initializes a new instance of the class and sets dark as the default theme. - /// - public PSMarkdownOptionInfo() - { - SetDarkTheme(); - EnableVT100Encoding = true; - } - - private const string Header1Dark = "[7m"; - private const string Header2Dark = "[4;93m"; - private const string Header3Dark = "[4;94m"; - private const string Header4Dark = "[4;95m"; - private const string Header5Dark = "[4;96m"; - private const string Header6Dark = "[4;97m"; - private const string CodeDark = "[48;2;155;155;155;38;2;30;30;30m"; - private const string CodeMacOS = "[107;95m"; - private const string LinkDark = "[4;38;5;117m"; - private const string ImageDark = "[33m"; - private const string EmphasisBoldDark = "[1m"; - private const string EmphasisItalicsDark = "[36m"; - - private const string Header1Light = "[7m"; - private const string Header2Light = "[4;33m"; - private const string Header3Light = "[4;34m"; - private const string Header4Light = "[4;35m"; - private const string Header5Light = "[4;36m"; - private const string Header6Light = "[4;30m"; - private const string CodeLight = "[48;2;155;155;155;38;2;30;30;30m"; - private const string LinkLight = "[4;38;5;117m"; - private const string ImageLight = "[33m"; - private const string EmphasisBoldLight = "[1m"; - private const string EmphasisItalicsLight = "[36m"; - - /// - /// Set all preference for dark theme. - /// - [MemberNotNull(nameof(Header1), - nameof(Header2), - nameof(Header3), - nameof(Header4), - nameof(Header5), - nameof(Header6), - nameof(Link), - nameof(Image), - nameof(EmphasisBold), - nameof(EmphasisItalics), - nameof(Code))] - public void SetDarkTheme() - { - Header1 = Header1Dark; - Header2 = Header2Dark; - Header3 = Header3Dark; - Header4 = Header4Dark; - Header5 = Header5Dark; - Header6 = Header6Dark; - Link = LinkDark; - Image = ImageDark; - EmphasisBold = EmphasisBoldDark; - EmphasisItalics = EmphasisItalicsDark; - SetCodeColor(isDarkTheme: true); - } - - /// - /// Set all preference for light theme. - /// - [MemberNotNull(nameof(Header1), - nameof(Header2), - nameof(Header3), - nameof(Header4), - nameof(Header5), - nameof(Header6), - nameof(Link), - nameof(Image), - nameof(EmphasisBold), - nameof(EmphasisItalics), - nameof(Code))] - public void SetLightTheme() - { - Header1 = Header1Light; - Header2 = Header2Light; - Header3 = Header3Light; - Header4 = Header4Light; - Header5 = Header5Light; - Header6 = Header6Light; - Link = LinkLight; - Image = ImageLight; - EmphasisBold = EmphasisBoldLight; - EmphasisItalics = EmphasisItalicsLight; - SetCodeColor(isDarkTheme: false); - } - - [MemberNotNull(nameof(Code))] - private void SetCodeColor(bool isDarkTheme) - { - // MacOS terminal app does not support extended colors for VT100, so we special case for it. - Code = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? CodeMacOS : isDarkTheme ? CodeDark : CodeLight; - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs deleted file mode 100644 index e063ccb0..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphBlockRenderer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Renderer for adding VT100 escape sequences for paragraphs. -/// -internal class ParagraphBlockRenderer : VT100ObjectRenderer -{ - protected override void Write(VT100Renderer renderer, ParagraphBlock obj) - { - if (obj.Inline == null) - return; - - // Call the renderer for children, leaf inline or line breaks. - renderer.WriteChildren(obj.Inline); - - // Add new line at the end of the paragraph. - renderer.WriteLine(); - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs new file mode 100644 index 00000000..b566102c --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ParagraphRenderer.cs @@ -0,0 +1,14 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class ParagraphRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, ParagraphBlock obj) + { + renderer + .WriteLeafInline(obj) + .WriteLine() + .WriteLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs index ae2639d0..8debad31 100644 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/QuoteBlockRenderer.cs @@ -1,24 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Syntax; +using Markdig.Syntax; namespace Bookgen.Lib.Markdown.Renderers.Terminal; -/// -/// Renderer for adding VT100 escape sequences for quote blocks. -/// -internal class QuoteBlockRenderer : VT100ObjectRenderer +internal sealed class QuoteBlockRenderer : TerminalObjectRenderer { - protected override void Write(VT100Renderer renderer, QuoteBlock obj) + protected override void Write(TerminalRenderer renderer, QuoteBlock obj) { - // Iterate through each item and add the quote character before the content. - foreach (var item in obj) - { - renderer.Write(obj.QuoteChar).Write(" ").Write(item); - } + var begin = renderer.Builder.New() + .WithForegroundColor(renderer.RenderOptions.QuoteBlockColor) + .WithItalic() + .ToString(); + + renderer + .Write(begin) + .WriteChildren(obj); + + renderer + .WriteReset(); - // Add blank line after the quote block. - renderer.WriteLine(); } } diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs new file mode 100644 index 00000000..a0953288 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/RenderOptions.cs @@ -0,0 +1,41 @@ +using Webmaster442.WindowsTerminal; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +public sealed class RenderOptions +{ + public TerminalColor HeadingColor { get; set; } + + public TerminalColor LinkColor { get; set; } + + public TerminalColor CodeInlineColor { get; set; } + + public TerminalColor QuoteBlockColor { get; set; } + + public TerminalColor CodeBlockBackground { get; set; } + + public TerminalColor CodeBlockColor { get; set; } + + public int Width + { + get => field; + set + { + if (value < 1) + throw new ArgumentOutOfRangeException(nameof(Width), "Width must be greater than 0."); + + field = value; + } + } + + public RenderOptions() + { + HeadingColor = TerminalColor.Green; + LinkColor = TerminalColor.BrightBlue; + CodeInlineColor = TerminalColor.BrightRed; + QuoteBlockColor = TerminalColor.BrightWhite; + CodeBlockColor = TerminalColor.Black; + CodeBlockBackground = TerminalColor.White; + Width = 120; + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs new file mode 100644 index 00000000..a97ebc24 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalObjectRenderer.cs @@ -0,0 +1,8 @@ +using Markdig.Renderers; +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +public abstract class TerminalObjectRenderer : MarkdownObjectRenderer where TObject : MarkdownObject +{ +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs new file mode 100644 index 00000000..a587a8ca --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/TerminalRenderer.cs @@ -0,0 +1,42 @@ +using Markdig.Renderers; + +using Webmaster442.WindowsTerminal; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +public sealed class TerminalRenderer : TextRendererBase +{ + + public TerminalRenderer(TextWriter writer, RenderOptions renderOptions) : base(writer) + { + RenderOptions = renderOptions; + Builder = new TerminalFormattedStringBuilder(); + + ObjectRenderers.Add(new CodeBlockRenderer()); + ObjectRenderers.Add(new ListRenderer()); + ObjectRenderers.Add(new HeadingRenderer()); + ObjectRenderers.Add(new ParagraphRenderer()); + ObjectRenderers.Add(new QuoteBlockRenderer()); + ObjectRenderers.Add(new ThematicBreakRenderer()); + + ObjectRenderers.Add(new AutolinkInlineRenderer()); + ObjectRenderers.Add(new CodeInlineRenderer()); + ObjectRenderers.Add(new DelimiterInlineRenderer()); + ObjectRenderers.Add(new EmphasisInlineRenderer()); + ObjectRenderers.Add(new LineBreakInlineRenderer()); + ObjectRenderers.Add(new LinkInlineRenderer()); + ObjectRenderers.Add(new LiteralInlineRenderer()); + } + + public RenderOptions RenderOptions { get; } + + internal TerminalFormattedStringBuilder Builder { get; } + + private const string ResetCode = "\u001b[0m"; + + public TerminalRenderer WriteReset() + { + Write(ResetCode); + return this; + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs new file mode 100644 index 00000000..18b01324 --- /dev/null +++ b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/ThematicBreakRenderer.cs @@ -0,0 +1,12 @@ +using Markdig.Syntax; + +namespace Bookgen.Lib.Markdown.Renderers.Terminal; + +internal sealed class ThematicBreakRenderer : TerminalObjectRenderer +{ + protected override void Write(TerminalRenderer renderer, ThematicBreakBlock obj) + { + renderer.WriteLine(new string('-', renderer.RenderOptions.Width)); + renderer.WriteLine(); + } +} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs deleted file mode 100644 index 6c174bd1..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100EscapeSequences.cs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Class to represent default VT100 escape sequences. -/// -public class VT100EscapeSequences -{ - private const char Esc = (char)0x1B; - - private readonly string _endSequence = Esc + "[0m"; - - // For code blocks, [500@ make sure that the whole line has background color. - private const string LongBackgroundCodeBlock = "[500@"; - - private readonly PSMarkdownOptionInfo _options; - - /// - /// Initializes a new instance of the class. - /// - /// PSMarkdownOptionInfo object to initialize with. - public VT100EscapeSequences(PSMarkdownOptionInfo optionInfo) - { - _options = optionInfo ?? throw new ArgumentNullException(nameof(optionInfo)); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 1 string. - public string FormatHeader1(string headerText) - { - return FormatHeader(headerText, _options.Header1); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 2 string. - public string FormatHeader2(string headerText) - { - return FormatHeader(headerText, _options.Header2); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 3 string. - public string FormatHeader3(string headerText) - { - return FormatHeader(headerText, _options.Header3); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 4 string. - public string FormatHeader4(string headerText) - { - return FormatHeader(headerText, _options.Header4); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 5 string. - public string FormatHeader5(string headerText) - { - return FormatHeader(headerText, _options.Header5); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 6 string. - public string FormatHeader6(string headerText) - { - return FormatHeader(headerText, _options.Header6); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the code block to format. - /// True if it is a inline code block, false otherwise. - /// Formatted code block string. - public string FormatCode(string codeText, bool isInline) - { - bool isVT100Enabled = _options.EnableVT100Encoding; - - if (isInline) - { - if (isVT100Enabled) - { - return string.Concat(Esc, _options.Code, codeText, _endSequence); - } - else - { - return codeText; - } - } - else - { - if (isVT100Enabled) - { - return string.Concat(Esc, _options.Code, codeText, Esc, LongBackgroundCodeBlock, _endSequence); - } - else - { - return codeText; - } - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the link to format. - /// URL of the link. - /// True url should be hidden, false otherwise. Default is true. - /// Formatted link string. - public string FormatLink(string linkText, string url, bool hideUrl = true) - { - bool isVT100Enabled = _options.EnableVT100Encoding; - - if (hideUrl) - { - if (isVT100Enabled) - { - return string.Concat(Esc, _options.Link, "\"", linkText, "\"", _endSequence); - } - else - { - return string.Concat("\"", linkText, "\""); - } - } - else - { - if (isVT100Enabled) - { - return string.Concat("\"", linkText, "\" (", Esc, _options.Link, url, _endSequence, ")"); - } - else - { - return string.Concat("\"", linkText, "\" (", url, ")"); - } - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text to format as emphasis. - /// True if it is to be formatted as bold, false to format it as italics. - /// Formatted emphasis string. - public string FormatEmphasis(string emphasisText, bool isBold) - { - var sequence = isBold ? _options.EmphasisBold : _options.EmphasisItalics; - - if (_options.EnableVT100Encoding) - { - return string.Concat(Esc, sequence, emphasisText, _endSequence); - } - else - { - return emphasisText; - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the image to format. - /// Formatted image string. - public string FormatImage(string? altText) - { - var text = altText; - - if (string.IsNullOrEmpty(altText)) - { - text = "Image"; - } - - if (_options.EnableVT100Encoding) - { - return string.Concat(Esc, _options.Image, "[", text, "]", _endSequence); - } - else - { - return string.Concat("[", text, "]"); - } - } - - private string FormatHeader(string headerText, string headerEscapeSequence) - { - if (_options.EnableVT100Encoding) - { - return string.Concat(Esc, headerEscapeSequence, headerText, _endSequence); - } - else - { - return headerText; - } - } -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs deleted file mode 100644 index 04f144c1..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100ObjectRenderer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Implement the MarkdownObjectRenderer with VT100Renderer. -/// -/// The element type of the renderer. -public abstract class VT100ObjectRenderer : MarkdownObjectRenderer where T : MarkdownObject -{ -} diff --git a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs b/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs deleted file mode 100644 index ff25494a..00000000 --- a/Source/Bookgen.Lib/Markdown/Renderers/Terminal/VT100Renderer.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Markdig.Renderers; - -namespace Bookgen.Lib.Markdown.Renderers.Terminal; - -/// -/// Initializes an instance of the VT100 renderer. -/// -public sealed class VT100Renderer : TextRendererBase -{ - /// - /// Initializes a new instance of the class. - /// - /// TextWriter to write to. - /// PSMarkdownOptionInfo object with options. - public VT100Renderer(TextWriter writer, PSMarkdownOptionInfo optionInfo) : base(writer) - { - EscapeSequences = new VT100EscapeSequences(optionInfo); - - // Add the various element renderers. - ObjectRenderers.Add(new HeaderBlockRenderer()); - ObjectRenderers.Add(new LineBreakRenderer()); - ObjectRenderers.Add(new CodeInlineRenderer()); - ObjectRenderers.Add(new FencedCodeBlockRenderer()); - ObjectRenderers.Add(new EmphasisInlineRenderer()); - ObjectRenderers.Add(new ParagraphBlockRenderer()); - ObjectRenderers.Add(new LeafInlineRenderer()); - ObjectRenderers.Add(new LinkInlineRenderer()); - ObjectRenderers.Add(new ListBlockRenderer()); - ObjectRenderers.Add(new ListItemBlockRenderer()); - ObjectRenderers.Add(new QuoteBlockRenderer()); - } - - /// - /// Gets the current escape sequences. - /// - public VT100EscapeSequences EscapeSequences { get; } -} diff --git a/Source/Bookgen.Lib/packages.lock.json b/Source/Bookgen.Lib/packages.lock.json index f155489c..c06f640c 100644 --- a/Source/Bookgen.Lib/packages.lock.json +++ b/Source/Bookgen.Lib/packages.lock.json @@ -4,9 +4,9 @@ "net10.0": { "Markdig": { "type": "Direct", - "requested": "[0.44.0, )", - "resolved": "0.44.0", - "contentHash": "X+CYMjcUnh/yO24wOSQxVFLiGqWrrtXJ5M7toHiM1Zk4Fg9UMLN5fkaq6FSOWH+mIprsHHgDMlq3MJhmrXalhg==" + "requested": "[0.45.0, )", + "resolved": "0.45.0", + "contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg==" }, "Microsoft.ClearScript": { "type": "Direct", @@ -35,42 +35,42 @@ }, "SkiaSharp": { "type": "Direct", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "+Ru1BTSZQne3Vp+vbSb50Ke3Nlc3ZnItxx4+751J9WZ8YzLKAV/n+9DAo4zFTyeCI//ueT63c+VybmTTpYBEiw==", + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "nmy2dOFWPvQKMglfpjz8+/xQQcSrL9jzul3cUyzCJVSwrmSAw+6B1sEgU7jt6NZBptwGq2k/V0kjyu2GizMFtg==", "dependencies": { - "SkiaSharp.NativeAssets.Win32": "3.119.1", - "SkiaSharp.NativeAssets.macOS": "3.119.1" + "SkiaSharp.NativeAssets.Win32": "3.119.2", + "SkiaSharp.NativeAssets.macOS": "3.119.2" } }, "SkiaSharp.NativeAssets.Linux": { "type": "Direct", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "9YNoc4SeKvQhrwiqwT4ezkNfMywPdPSK+UFvo/CaoXqLixcnYOTsQKm5BF9mc4+q3vKgDtEgMt0d1ygZhJTEHg==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "9WzxSyG/s9Id506j0Ht+Bi5ucOpWKPzd1XXr9TD4fuCafHHy2swRSlbZtC3IDQAsvCH63OerkDJajj43uSv5og==" }, "SkiaSharp.NativeAssets.Win32": { "type": "Direct", - "requested": "[3.119.1, )", - "resolved": "3.119.1", - "contentHash": "8C4GSXVJqSr0y3Tyyv5jz6MJSTVUyYkMjeKrzK+VyZPGLo89MNoUEclVuYahzOCDdtbfXrd2HtxXfDuvoSXrUw==" + "requested": "[3.119.2, )", + "resolved": "3.119.2", + "contentHash": "uYe+da6+GXVgPKkCopzvIZ83DmC8SXXKeUAPrNcztJNsg0SjPQAxfKMOPZqmVjbzznrq/QUIjLUlJSZV/e0IPA==" }, "Svg.Skia": { "type": "Direct", - "requested": "[3.2.1, )", - "resolved": "3.2.1", - "contentHash": "pRJtMOc2hTcnxSu7cx6lf806S+2VrQ0eqUpGMBUQQ/pIIvzrtn0+lUjdABTvjr5rsdQF9OxE+r9npq2+3c6T4A==", + "requested": "[3.4.1, )", + "resolved": "3.4.1", + "contentHash": "fNGHIeIUtEDo41P4MYfVqjNL902t8EP5/tBY9vYDg8VbKSOD84ZlBfCL4KSxjf//HcJi1Uz4uZCUXuEAKJirCw==", "dependencies": { "SkiaSharp": "2.88.9", - "Svg.Custom": "3.2.1", - "Svg.Model": "3.2.1" + "Svg.Custom": "3.4.1", + "Svg.Model": "3.4.1" } }, "System.ServiceModel.Syndication": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "Z3+s66hOp7JVDOhkVk0gH/sjsshn99NUMiILcXLaA+3qe2OkLr3nB+3bRzXdfF6arlJBxGfkkki9GP6R9kvdFg==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "mloJBxfbjYXgfcfMvH40UWwzekATlUzHLMKPQpg4qab+gpHRgQkhgo4DLz3v7Kacn6000sqoNkxiyk0pJ5x/ig==" }, "YamlDotNet": { "type": "Direct", @@ -131,29 +131,29 @@ }, "ShimSkiaSharp": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "WyMf+0aj5IJcHkG/t89WApGFg756oXB30ehu9kYu9hjdfS0snkXHoAiTJZqVlqyLOvzC6eA+nOQ2hI84g//5Pg==" + "resolved": "3.4.1", + "contentHash": "ab3J5OGdwYLyXnom90OghS9NyRPH9dtsRtW5B3mL+F43YTlxido3nUtiR9NjB/sI3jvG/J744C29bsSBQ28AfQ==" }, "SkiaSharp.NativeAssets.macOS": { "type": "Transitive", - "resolved": "3.119.1", - "contentHash": "6hR3BdLhApjDxR1bFrJ7/lMydPfI01s3K+3WjIXFUlfC0MFCFCwRzv+JtzIkW9bDXs7XUVQS+6EVf0uzCasnGQ==" + "resolved": "3.119.2", + "contentHash": "I2jMGQ/26KOnc6iAoR+Mxh9vSJJ2vioJyj9aJ9OL5yEZyXothXJxf4vBMqnSaiXMqiiU1scG7KqtT0CLkmMmWA==" }, "Svg.Custom": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "Wu0+Il+SZfY5+AT25b2PMlSbBrzGIo/6vMMMdOK8jd25I9tdJJbxOFJNG87gFgo9UY9OmpIFzVobGOHzFdJYeQ==", + "resolved": "3.4.1", + "contentHash": "ksHa7Zv8X1InxSWAeM0sSSvZM2W6FWgneXjU0bdYrQ20Q6q0nl3g0uaSWe53i/CC1OmR+/JAxsp8E3/wigtcng==", "dependencies": { "ExCSS": "4.3.1" } }, "Svg.Model": { "type": "Transitive", - "resolved": "3.2.1", - "contentHash": "N4yWlvqv1XWZ+VvI29lT4HkEgtiP3rS/poMfWiVzz6Ao/3IjQCfmo2qrKphq77IqTdMYyh7wGDknMId7YrQY5Q==", + "resolved": "3.4.1", + "contentHash": "Zn04CWWIwV+qlJ4x4vg8FdCYNew5dRcsvkcCpzZggFKHGExIn4WTBtJMuLH8htuiLnbbgF6woIYpUll+seHfjw==", "dependencies": { - "ShimSkiaSharp": "3.2.1", - "Svg.Custom": "3.2.1" + "ShimSkiaSharp": "3.4.1", + "Svg.Custom": "3.4.1" } }, "bookgen.shell.shared": { diff --git a/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs b/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs new file mode 100644 index 00000000..6c7ad7ce --- /dev/null +++ b/Test/Bookgen.Tests/Lib/UT_JsonMerger.cs @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// (c) 2019-2026 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Text.Json; +using System.Text.Json.Nodes; + +using Bookgen.Lib.Confighandling; +using Bookgen.Lib.Domain.IO.Configuration; + +namespace Bookgen.Tests.Lib; + +[TestFixture] +public class UT_JsonMerger +{ + [Test] + public void EnsureThat_Merge_Works() + { + var config1 = new Config + { + Book2LetterISO639Language = "en", + BookAuthor = "Author One", + PrintConfig = new PrintConfig + { + Images = new ImageConfig + { + ImageQualityOnResize = 70, + ResizeAndRecodeImages = ImgRecodeOption.AsPng, + SvgRecode = SvgRecodeOption.AsPng, + } + }, + WordpressConfig = new WordpressConfig + { + CssClasses = new CssClasses + { + H1 = "custom-h1", + H2 = "custom-h2", + } + }, + }; + + var overlay = """ + { + "BookAuthor": "Author Two", + "PrintConfig": { + "Images": { + "ImageQualityOnResize": 85 + } + }, + "WordpressConfig": { + "CssClasses": { + "H2": "overridden-h2", + "H3": "custom-h3" + } + } + } + """; + + var node1 = JsonSerializer.SerializeToNode(config1) as JsonObject; + var nodeOverlay = JsonNode.Parse(overlay) as JsonObject; + + JsonMerger sut = new JsonMerger(node1!); + sut.Merge(nodeOverlay!); + + var result = sut.Deserialize(); + + Assert.That(result, Is.Not.Null); + + using (Assert.EnterMultipleScope()) + { + Assert.That(result!.Book2LetterISO639Language, Is.EqualTo("en")); + Assert.That(result.BookAuthor, Is.EqualTo("Author Two")); + Assert.That(result.PrintConfig.Images.ImageQualityOnResize, Is.EqualTo(85)); + Assert.That(result.PrintConfig.Images.ResizeAndRecodeImages, Is.EqualTo(ImgRecodeOption.AsPng)); + Assert.That(result.PrintConfig.Images.SvgRecode, Is.EqualTo(SvgRecodeOption.AsPng)); + Assert.That(result.WordpressConfig.CssClasses.H1, Is.EqualTo("custom-h1")); + Assert.That(result.WordpressConfig.CssClasses.H2, Is.EqualTo("overridden-h2")); + Assert.That(result.WordpressConfig.CssClasses.H3, Is.EqualTo("custom-h3")); + } + } +} diff --git a/publish.ps1 b/publish.ps1 index cdfe4433..f67a2c75 100644 --- a/publish.ps1 +++ b/publish.ps1 @@ -1,31 +1,63 @@ Clear-Host -# publish windows & linux self-contained -dotnet publish -c release -o "bin\publish\windows\bin" --self-contained true -r win-x64 -p PublishReadyToRun BookGen.slnx -dotnet publish -c release -o "bin\publish\linux\bin" --self-contained true -r linux-x64 -p PublishReadyToRun BookGen.slnx - -# copy installer scripts -Copy-Item "Installers\install.cmd" "bin\publish\windows\install.cmd" -Copy-Item "Installers\install.sh" "bin\publish\linux\install.sh" - -# copy assets -Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" -Copy-Item "bin\Release\assets.zip" "bin\publish\linux\bin\assets.zip" - -# write version.txt -.\bin\publish\windows\bin\BookGen.exe version > .\bin\publish\windows\version.txt -.\bin\publish\windows\bin\BookGen version > .\bin\publish\linux\version.txt - -# Generate docs -.\bin\publish\windows\bin\BookGen Schemas -.\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\windows\Schemas.html" -t "Configuration schemas" -.\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\linux\Schemas.html" -t "Configuration schemas" -.\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\windows\Changelog.html" -t "Change Log" -.\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\linux\Changelog.html" -t "Change Log" -.\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\windows\Commands.html" -t "BookGen Commands" -.\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\linux\Commands.html" -t "BookGen Commands" -Remove-Item Schemas.md - -# zip -Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\BookGen-windows.zip" -Force -tar -czvf "bin\publish\BookGen-linux.tar.gz" -C "bin\publish\linux" . +function Invoke-Publish { + param( + [bool] $SelfContained, + [string] $WindowsArchiveName, + [string] $LinuxArchiveName + ) + + if (Test-Path "bin\publish\windows") { + Remove-Item "bin\publish\windows*" -Recurse -Force + } + + if (Test-Path "bin\publish\linux") { + Remove-Item "bin\publish\windows*" -Recurse -Force + } + + # publish windows & linux + if ($SelfContained) { + dotnet publish -c release -o "bin\publish\windows\bin" --self-contained true -r win-x64 -p PublishReadyToRun BookGen.slnx + dotnet publish -c release -o "bin\publish\linux\bin" --self-contained true -r linux-x64 -p PublishReadyToRun BookGen.slnx + } + else { + dotnet publish -c release -o "bin\publish\windows\bin" -r win-x64 -p PublishReadyToRun BookGen.slnx + dotnet publish -c release -o "bin\publish\linux\bin" -r linux-x64 -p PublishReadyToRun BookGen.slnx + } + + # copy installer scripts + Copy-Item "Installers\install.cmd" "bin\publish\windows\install.cmd" + + # copy assets + Copy-Item "bin\Release\assets.zip" "bin\publish\windows\bin\assets.zip" + Copy-Item "bin\Release\assets.zip" "bin\publish\linux\bin\assets.zip" + + # write version.txt + .\bin\publish\windows\bin\BookGen.exe version > .\bin\publish\windows\version.txt + .\bin\publish\windows\bin\BookGen version > .\bin\publish\linux\version.txt + + # Generate docs + .\bin\publish\windows\bin\BookGen Schemas + .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\windows\Schemas.html" -t "Configuration schemas" + .\bin\publish\windows\bin\BookGen md2html -i Schemas.md -o "bin\publish\linux\Schemas.html" -t "Configuration schemas" + .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\windows\Changelog.html" -t "Change Log" + .\bin\publish\windows\bin\BookGen md2html -i Changelog.md -o "bin\publish\linux\Changelog.html" -t "Change Log" + .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\windows\Commands.html" -t "BookGen Commands" + .\bin\publish\windows\bin\BookGen md2html -i Commands.md -o "bin\publish\linux\Commands.html" -t "BookGen Commands" + Remove-Item Schemas.md + + # zip + if ($SelfContained) { + Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\$WindowsArchiveName" -Force + tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . + } + else { + Compress-Archive -Path "bin\publish\windows\*" -DestinationPath "bin\publish\$WindowsArchiveName" -Force + tar -czvf "bin\publish\$LinuxArchiveName" -C "bin\publish\linux" . + } +} + +# Self-contained build and archives +Invoke-Publish -SelfContained $true -WindowsArchiveName "BookGen-windows-selefcontained.zip" -LinuxArchiveName "BookGen-linux-selefcontained.tar.gz" +# Framework-dependent build and archives +Invoke-Publish -SelfContained $false -WindowsArchiveName "BookGen-windows.zip" -LinuxArchiveName "BookGen-linux.tar.gz" \ No newline at end of file