diff --git a/CHANGELOG.md b/CHANGELOG.md index b0931e0..0d3dafa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ # CHANGELOG + +## Fred 0.5.0 - di 7 maart 2023 + +- BREAKING: renamed and reordered multiple commands +- feature: unset_key +- feature: toggle_bool_val +- feature: set_bool_val, set_string_val + ## Fred 0.4.0 - vr 17 feb 2023 - rename taxo to front-matter everywhere - feature: set_key_val diff --git a/README.md b/README.md index b4cd3d9..7dc46ab 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,15 @@ Fred is a cli utility for precisely editing YAML-nodes inside the front matter of a markdown file. ## Features + +- Unset key - Rename the key of a scalar node -- Replace value of scalar node +- Replace string value of scalar node +- Toggle bool value of scalar node - Scaler node at 1st level are automatically defined as variable - Substitute variables inside scalar node when it's defined lines earlier +- Recursive mode for processing files inside directories +- Dry run mode ## Installation @@ -28,23 +33,24 @@ of a markdown file. ## Usage ```bash - Usage: - - fred help Options: --help Show this help. - -d, --dryrun Dry run. Output only [type:Bool] - -r, --recursive Path is a directory. All .md files in the directory will be processed [type:Bool] - -v, --verbose Be verbose [type:Bool] Sub Commands: + echo echo display one node by key + set_bool_val Set boolean value for front matter key + set_string_val Set string value for front matter key + unset_key Remove key from front matter + replace_key Find and replace key in front matter + replace_string_val Find and replace a string value in front matter + toggle_bool_val Toggle a bool value in front matter, if true set false, if false or missing set true + replace_1st_level_vars replace variables found on 1st level in other levels in inside front matter + replace_includes replace includes inside front matter + process_functions replace $FORMAT and $INCLUDE inside front matter version version - replace_1st_level_vars replace 1st level variables in inside the front matter - rename_taxo_key rename a taxo string val - rename_taxo_val rename a taxo string val in a single file ``` ## Variable usage @@ -109,7 +115,6 @@ make run_coverage make build ``` - ## Contributing 1. Fork it () diff --git a/shard.yml b/shard.yml index 4590840..6e60bfb 100644 --- a/shard.yml +++ b/shard.yml @@ -1,8 +1,8 @@ name: fred -version: 0.4.0 +version: 0.5.0 authors: - - Pim Snel + - Pim Snel targets: fred: diff --git a/spec/testfiles/markdown_2.md b/spec/testfiles/markdown_2.md index 384fcc9..a96805b 100644 --- a/spec/testfiles/markdown_2.md +++ b/spec/testfiles/markdown_2.md @@ -2,8 +2,9 @@ key1: val_old key2: üòmlaubt key3: - - hallo - - goodby +- hallo +- goodby +test: true --- This is some text. diff --git a/src/fred.cr b/src/fred.cr index 3333be5..83b1695 100644 --- a/src/fred.cr +++ b/src/fred.cr @@ -14,13 +14,6 @@ module Fred puts opts.help_string end - sub "version" do - desc "version" - usage "fred version" - run do |opts, args| - puts Fred::VERSION - end - end sub "echo" do desc "echo display one node by key" @@ -42,8 +35,9 @@ module Fred end end - sub "process_frontmatter_specials" do - desc "replace $FORMAT and $INCLUDE inside the front matter" + + sub "set_bool_val" do + desc "Set boolean value for front matter key" option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" @@ -54,16 +48,28 @@ module Fred type: String, required: true - usage "fred process_frontmatter_specials [PATH]" + argument "front_matter_key", + desc: "key to set", + type: String, + required: true + + argument "front_matter_val", + desc: "true or false", + type: Bool, + required: true + + usage "fred set_bool_val [PATH] [FRONT_MATTER_KEY] [true / false] [options]" + run do |opts, args| path = args.path + fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) - fs_processor.process_all_specials + fs_processor.set_key_val(args.front_matter_key, args.front_matter_val) end end - sub "set_key_val" do - desc "Set key and value in the root Front Matter" + sub "set_string_val" do + desc "Set string value for front matter key" option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" @@ -73,16 +79,18 @@ module Fred desc: "directory or file", type: String, required: true + argument "front_matter_key", desc: "key to set", type: String, required: true + argument "front_matter_val", - desc: "value for the key", + desc: "string value to set", type: String, required: true - usage "fred set_key_val [PATH] [FRONT_MATTER_KEY] [FRONT_MATTER_VAL]" + usage "fred set_string_val [PATH] [KEY] [VAL] [options]" run do |opts, args| path = args.path @@ -91,8 +99,8 @@ module Fred end end - sub "replace_1st_level_vars" do - desc "replace 1st level variables in inside the front matter" + sub "unset_key" do + desc "Remove key from front matter" option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" @@ -103,17 +111,22 @@ module Fred type: String, required: true - usage "fred replace_1st_level_vars [PATH]" + argument "unset_key", + desc: "key to remove", + type: String, + required: true + + usage "fred unset_key [PATH] [KEY] [options]" run do |opts, args| path = args.path fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) - fs_processor.replace_1st_level_vars + fs_processor.unset_front_matter_key(args.unset_key) end end - sub "replace_includes" do - desc "replace includes inside the front matter" + sub "replace_key" do + desc "Find and replace key in front matter" option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" @@ -124,74 +137,153 @@ module Fred type: String, required: true - usage "fred replace_includes [PATH] [options]" + argument "key_old", + desc: "key to replace", + type: String, + required: true + + argument "key_new", + desc: "new key", + type: String, + required: true + + usage "fred replace_key [PATH] [KEY_OLD] [KEY_NEW] [options]" + run do |opts, args| path = args.path fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) - fs_processor.replace_includes + fs_processor.rename_front_matter_key(args.key_old, args.key_new) end end - sub "rename_front_matter_key" do - desc "Rename a Front Matter key" + sub "replace_string_val" do + desc "Find and replace a string value in front matter" option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" option "-v", "--verbose", type: Bool, desc: "Be verbose" - argument "front_matter_key_old", - desc: "key to replace", + argument "path", + desc: "directory or file", type: String, required: true - argument "front_matter_key_new", - desc: "new key", + + argument "front_matter_key", + desc: "key to replace value for", type: String, required: true - argument "path", - desc: "directory or file", + argument "front_matter_val_old", + desc: "old value", + type: String, + required: true + argument "front_matter_val_new", + desc: "new value", type: String, required: true - usage "fred rename_front_matter_key [front_matter_key_old] [front_matter_key_new] [PATH]" + usage "fred replace_string_val [PATH] [KEY] [VAL_OLD] [VAL_NEW] [options]" run do |opts, args| - path = args.path - fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) - fs_processor.rename_front_matter_key(args.front_matter_key_old, args.front_matter_key_new) + fs_processor = FSProcessor.new(args.path, opts.dryrun, opts.recursive, opts.verbose) + fs_processor.rename_front_matter_val(args.front_matter_key, args.front_matter_val_old, args.front_matter_val_new) end end - sub "rename_front_matter_val" do - desc "Rename a Front Matter string value in a single file" + sub "toggle_bool_val" do + desc "Toggle a bool value in front matter, if true set false, if false or missing set true" option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" option "-v", "--verbose", type: Bool, desc: "Be verbose" + argument "path", + desc: "directory or file", + type: String, + required: true + argument "front_matter_key", - desc: "key to replace value for", + desc: "key to toggle value for", type: String, required: true - argument "front_matter_val_old", - desc: "old value", + + usage "fred toggle_bool_val [PATH] [KEY] [options]" + + run do |opts, args| + fs_processor = FSProcessor.new(args.path, opts.dryrun, opts.recursive, opts.verbose) + fs_processor.toggle_bool_val(args.front_matter_key) + end + end + + sub "replace_1st_level_vars" do + desc "replace variables found on 1st level in other levels in inside front matter" + + option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" + option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" + option "-v", "--verbose", type: Bool, desc: "Be verbose" + + argument "path", + desc: "directory or file", type: String, required: true - argument "front_matter_val_new", - desc: "new value", + + usage "fred replace_1st_level_vars [PATH] [options]" + + run do |opts, args| + path = args.path + fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) + fs_processor.replace_1st_level_vars + end + end + + sub "replace_includes" do + desc "replace includes inside front matter" + + option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" + option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" + option "-v", "--verbose", type: Bool, desc: "Be verbose" + + argument "path", + desc: "directory or file", type: String, required: true + + usage "fred replace_includes [PATH] [options]" + run do |opts, args| + path = args.path + fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) + fs_processor.replace_includes + end + end + + sub "process_functions" do + desc "replace $FORMAT and $INCLUDE inside front matter" + + option "-d", "--dryrun", type: Bool, desc: "Dry run. Output only" + option "-r", "--recursive", type: Bool, desc: "Path is a directory. All .md files in the directory will be processed" + option "-v", "--verbose", type: Bool, desc: "Be verbose" + argument "path", desc: "directory or file", type: String, required: true - usage "fred rename_front_matter_val [front_matter_key] [front_matter_val_old] [front_matter_val_new] [PATH]" + usage "fred process_functions [PATH] [options]" + run do |opts, args| + path = args.path + fs_processor = FSProcessor.new(path, opts.dryrun, opts.recursive, opts.verbose) + fs_processor.process_all_specials + end + end + + sub "version" do + desc "version" + usage "fred version" run do |opts, args| - fs_processor = FSProcessor.new(args.path, opts.dryrun, opts.recursive, opts.verbose) - fs_processor.rename_front_matter_val(args.front_matter_key, args.front_matter_val_old, args.front_matter_val_new) + puts Fred::VERSION end end + end end end diff --git a/src/fred/fs_processor.cr b/src/fred/fs_processor.cr index b162eb4..f51a033 100644 --- a/src/fred/fs_processor.cr +++ b/src/fred/fs_processor.cr @@ -46,6 +46,18 @@ class FSProcessor report_command_stats if @verbose end + def toggle_bool_val(key) + @files.each do |in_file| + begin + toggle_bool_val_in_file(in_file, key) + rescue e + print "\nError: " + e.to_s + " in " + in_file + "\n" + end + end + + report_command_stats if @verbose + end + def set_key_val(key,val) @files.each do |in_file| @@ -83,6 +95,18 @@ class FSProcessor report_command_stats if @verbose end + def unset_front_matter_key(key) + @files.each do |in_file| + begin + unset_front_matter_key_in_file(in_file, key) + rescue + p in_file + " has invalid Front Matter." + end + end + + report_command_stats if @verbose + end + def rename_front_matter_key(key_old, key_new) @files.each do |in_file| begin @@ -116,6 +140,12 @@ class FSProcessor output_markdown_doc(in_file, markdown_doc) end + private def toggle_bool_val_in_file(in_file, key) + markdown_doc = MarkdownDoc.new(in_file, @only_output_when_changed) + markdown_doc.toggle_bool_val_to_frontmatter(key) + output_markdown_doc(in_file, markdown_doc) + end + private def set_key_val_in_file(in_file, key, val) markdown_doc = MarkdownDoc.new(in_file, @only_output_when_changed) markdown_doc.set_key_val_to_frontmatter(key, val) @@ -134,6 +164,12 @@ class FSProcessor output_markdown_doc(in_file, markdown_doc) end + private def unset_front_matter_key_in_file(in_file, key) + markdown_doc = MarkdownDoc.new(in_file, @only_output_when_changed) + markdown_doc.unset_front_matter_key(key) + output_markdown_doc(in_file, markdown_doc) + end + private def rename_front_matter_key_in_file(in_file, key_old, key_new) markdown_doc = MarkdownDoc.new(in_file, @only_output_when_changed) markdown_doc.rename_front_matter_key(key_old, key_new) diff --git a/src/fred/markdown_doc.cr b/src/fred/markdown_doc.cr index 1a3e03e..416f789 100644 --- a/src/fred/markdown_doc.cr +++ b/src/fred/markdown_doc.cr @@ -21,6 +21,7 @@ class MarkdownDoc @infile = infile @changed = false @doc_stats = {} of Symbol => Int32 + @doc_stats[:unset_keys_num] = 0 @doc_stats[:set_key_val_num] = 0 @doc_stats[:replaced_keys_num] = 0 @doc_stats[:replaced_vals_num] = 0 @@ -28,6 +29,11 @@ class MarkdownDoc @doc_stats[:replaced_include_yaml_num] = 0 end + def toggle_bool_val_to_frontmatter(front_matter_key) + yaml_processor = YamlHashProcessor.new(@front_matter_as_yaml) + yaml_processor.process_node_toggle_key_bool_value(front_matter_key) + store_process_data(yaml_processor) + end def set_key_val_to_frontmatter(front_matter_key, front_matter_val) yaml_processor = YamlHashProcessor.new(@front_matter_as_yaml) @@ -48,6 +54,12 @@ class MarkdownDoc store_process_data(yaml_processor) end + def unset_front_matter_key(front_matter_key) + yaml_processor = YamlHashProcessor.new(@front_matter_as_yaml) + yaml_processor.process_node_unset_front_matter_key(front_matter_key) + store_process_data(yaml_processor) + end + def rename_front_matter_key(front_matter_key_old, front_matter_key_new) yaml_processor = YamlHashProcessor.new(@front_matter_as_yaml) yaml_processor.process_node_replace_front_matter_key(front_matter_key_old, front_matter_key_new) @@ -114,6 +126,7 @@ class MarkdownDoc print "\n" print "Stats for " + @infile + "\n" print " Add YAML key/val combinations: " + @doc_stats[:set_key_val_num].to_s + "\n" + print " Unset YAML keys: " + @doc_stats[:unset_keys_num].to_s + "\n" print " Replaced YAML keys: " + @doc_stats[:replaced_keys_num].to_s + "\n" print " Replaced YAML vals: " + @doc_stats[:replaced_vals_num].to_s + "\n" print " Replaced $FORMAT in YAML scalars: " + @doc_stats[:replaced_formats_vars_num].to_s + "\n" diff --git a/src/fred/version.cr b/src/fred/version.cr index b47646f..0d1dc0d 100644 --- a/src/fred/version.cr +++ b/src/fred/version.cr @@ -1,3 +1,3 @@ module Fred - VERSION = "0.4.0" + VERSION = "0.5.0" end diff --git a/src/fred/yaml_hash_processor.cr b/src/fred/yaml_hash_processor.cr index 549e674..b26c1e6 100644 --- a/src/fred/yaml_hash_processor.cr +++ b/src/fred/yaml_hash_processor.cr @@ -7,6 +7,7 @@ class YamlHashProcessor def initialize(front_matter_as_yaml : YAML::Any) @set_key_val_num = 0 + @unset_keys_num = 0 @replaced_keys_num = 0 @replaced_vals_num = 0 @replaced_formats_vars_num = 0 @@ -41,10 +42,18 @@ class YamlHashProcessor @front_matter_as_yaml = _node_replace_front_matter_val(@front_matter_as_yaml, front_matter_key, front_matter_val_old, front_matter_val_new) end + def process_node_toggle_key_bool_value(key) + @front_matter_as_yaml = _node_toggle_key_bool_val(@front_matter_as_yaml, key) + end + def process_node_set_key_value(key, val) @front_matter_as_yaml = _node_set_key_val(@front_matter_as_yaml, key, val) end + def process_node_unset_front_matter_key(front_matter_key) + @front_matter_as_yaml = _node_unset_front_matter_key(@front_matter_as_yaml, front_matter_key) + end + def process_node_replace_front_matter_key(front_matter_key_old, front_matter_key_new) @front_matter_as_yaml = _node_replace_front_matter_key(@front_matter_as_yaml, front_matter_key_old, front_matter_key_new) end @@ -52,6 +61,7 @@ class YamlHashProcessor def process_stats proc_stats = {} of Symbol => Int32 proc_stats[:set_key_val_num] = @set_key_val_num + proc_stats[:unset_keys_num] = @unset_keys_num proc_stats[:replaced_keys_num] = @replaced_keys_num proc_stats[:replaced_vals_num] = @replaced_vals_num proc_stats[:replaced_formats_vars_num] = @replaced_formats_vars_num @@ -61,6 +71,7 @@ class YamlHashProcessor end def replaced_any + return true if @unset_keys_num > 0 return true if @set_key_val_num > 0 return true if @replaced_keys_num > 0 return true if @replaced_vals_num > 0 @@ -149,6 +160,39 @@ class YamlHashProcessor return new_string end + + private def _node_toggle_key_bool_val(node : YAML::Any, front_matter_key) + case node.raw + when Hash(YAML::Any, YAML::Any) + + new_node = {} of YAML::Any => YAML::Any + if node.as_h.keys.includes?(front_matter_key) + node.as_h.each do |key, value| + if key.as_s == front_matter_key + if value.as_bool == true + new_node[YAML::Any.new(key.as_s)] = YAML::Any.new(false) + else + new_node[YAML::Any.new(key.as_s)] = YAML::Any.new(true) + end + @set_key_val_num += 1 + else + new_node[YAML::Any.new(key.as_s)] = value + end + end + else + node.as_h.each do |key, value| + new_node[YAML::Any.new(key.as_s)] = value + end + new_node[YAML::Any.new(front_matter_key)] = YAML::Any.new(true) + @set_key_val_num += 1 + end + + return YAML::Any.new(new_node) + end + + return node + end + private def _node_set_key_val(node : YAML::Any, front_matter_key, front_matter_val) case node.raw when Hash(YAML::Any, YAML::Any) @@ -164,6 +208,35 @@ class YamlHashProcessor return node end + private def _node_unset_front_matter_key(node : YAML::Any, front_matter_key) + case node.raw + when String + return node + when Array(YAML::Any) + new_node = [] of YAML::Any + node.as_a.each do |value| + new_node << _node_unset_front_matter_key(value, front_matter_key) + end + return YAML::Any.new(new_node) + when Hash(YAML::Any, YAML::Any) + new_node = {} of YAML::Any => YAML::Any + node.as_h.each do |key, value| + if key.as_s == front_matter_key + @unset_keys_num += 1 + else + new_node[YAML::Any.new(key.as_s)] = value + end + end + + return YAML::Any.new(new_node) + else + return node + end + + node + end + + private def _node_replace_front_matter_key(node : YAML::Any, front_matter_key_old, front_matter_key_new) case node.raw when String