From 580bb1568353274754d7aa22462fc4d045ef8192 Mon Sep 17 00:00:00 2001 From: Nicolas Bossard Date: Tue, 29 Apr 2025 18:44:15 +0200 Subject: [PATCH 1/6] feat: first basic hardcoded version of keywords support --- CHANGELOG.md | 1 + mtt_md_to_taskwarrior.sh | 31 ++++++++++++++++++- tests/Makefile | 2 ++ .../.gitignore | 1 + .../Makefile | 26 ++++++++++++++++ .../existing_markdown_file.md | 6 ++++ .../expected_tasks.ndjson | 16 ++++++++++ 7 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/.gitignore create mode 100644 tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/Makefile create mode 100644 tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md create mode 100644 tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson diff --git a/CHANGELOG.md b/CHANGELOG.md index c6dc788..e39855d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added script directory detection to make installation location independent - Added check that required programs are well available - Added tests on tags good working +- Begin support of keywords like "MR242" or "JIRA:OMD-3456" to be converted to annotations with URL ### Changed diff --git a/mtt_md_to_taskwarrior.sh b/mtt_md_to_taskwarrior.sh index 13f261e..64c6a13 100755 --- a/mtt_md_to_taskwarrior.sh +++ b/mtt_md_to_taskwarrior.sh @@ -190,6 +190,29 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while echo "converted priority : $priority" fi + ## extract the merge request number and convert it to annotation + ## e.g. : "MR414" in description should be converted to annotation : + ## {"entry":"20120110T234559Z","description":"https://gitlab.tech.orange/OrangeMoney/Retailer/pilotagedistri/business-server/-/merge_requests/414"} + mr_number=$(echo "$line" | grep -o 'MR[0-9]\+' | head -n 1) + mr_annotations="" + if [ -n "$mr_number" ]; then + echo "found merge request: $mr_number" + mr_num=${mr_number#MR} # Remove 'MR' prefix to get just the number + mr_annotations="{\"description\":\"https://gitlab.tech.orange/OrangeMoney/Retailer/pilotagedistri/business-server/-/merge_requests/$mr_num\"}" + fi + + # Handle JIRA ticket + jira_ticket=$(echo "$line" | grep -o -E '(JIRA:)?OMD-[0-9]+' | head -n 1) + if [ -n "$jira_ticket" ]; then + echo "found JIRA ticket: $jira_ticket" + # Remove JIRA: prefix if present + clean_ticket=${jira_ticket#JIRA:} + if [ -n "$mr_annotations" ]; then + mr_annotations+="," + fi + mr_annotations+="{\"description\":\"https://jira.tech.orange/browse/$clean_ticket\"}" + fi + # Extract all @ tags # CONFLICT @ concept does not exist in taskwarrior, doing nothing for now # at_tags=$(echo "$line" | grep -o '@[[:alnum:]]\+' | sed 's/@//' | tr '\n' ',' | sed 's/,$//') @@ -232,7 +255,13 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while [ -n "$project_name" ] && json+=",\"project\":\"$project_name\"" [ -n "$priority" ] && json+=",\"priority\":\"$priority\"" [ -n "$all_tags" ] && json+=",\"tags\":[\"$(echo "$all_tags" | sed 's/,/\",\"/g')\"]" - json+=",\"annotations\":[{\"description\":\"Source: $abs_file_path\"}]" + + # Add all annotations (source, MR, and JIRA) + json+=",\"annotations\":[{\"description\":\"Source: $abs_file_path\"}" + if [ -n "$mr_annotations" ]; then + json+=",$mr_annotations" + fi + json+="]" json+="}" echo "$json" >> "$output_file" diff --git a/tests/Makefile b/tests/Makefile index f15e065..3a462ef 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,6 +3,7 @@ test: $(MAKE) -C test_md_to_taskwarrior_2_with_priority $@ $(MAKE) -C test_md_to_taskwarrior_3_with_dates $@ $(MAKE) -C test_md_to_taskwarrior_4_with_tags $@ + $(MAKE) -C test_md_to_taskwarrior_5_with_MR_and_JIRA $@ $(MAKE) -C test_taskwarrior_to_md_1 $@ $(MAKE) -C test_taskwarrior_to_md_2_non_existing_source_file $@ $(MAKE) -C test_taskwarrior_to_md_3_with_dependency $@ @@ -22,6 +23,7 @@ clean: $(MAKE) -C test_add_uuids_3_multiple_files $@ $(MAKE) -C test_add_uuids_4_dependencies_single_file $@ $(MAKE) -C test_add_uuids_5_dependencies_multiple_files $@ + $(MAKE) -C test_md_to_taskwarrior_5_with_MR_and_JIRA $@ $(MAKE) -C test_check_requirements $@ $(MAKE) -C test_md_to_taskwarrior_1 $@ $(MAKE) -C test_md_to_taskwarrior_2_with_priority $@ diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/.gitignore b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/Makefile b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/Makefile new file mode 100644 index 0000000..ab475ef --- /dev/null +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/Makefile @@ -0,0 +1,26 @@ +# generate the markdown file modified by mtt_taskwarrior_to_md +tmp/tasks.ndjson: existing_markdown_file.md \ + ../../mtt_md_to_taskwarrior.sh \ + Makefile + ../../mtt_md_to_taskwarrior.sh + mv tasks.ndjson tmp/tasks.ndjson + +# remove the path in annotation.description, keep only the filename for easy comparison +tmp/tasks_removed_path.ndjson: tmp/tasks.ndjson + jq 'if .annotations then .annotations |= map(.description |= (if startswith("Source: ") then "Source: " + (.[8:] | split("/")[-1]) else . end)) else . end' $< > $@ + + +clean: + rm -f tmp/* + +test: tmp/tasks_removed_path.ndjson + $(call .cecho,"Comparing files with cmp and diff") + @if cmp -s expected_tasks.ndjson $<; then \ + echo "✅ TEST PASSED : Files are identical"; \ + else \ + echo "❌ Files are different:"; \ + DIFF_OUTPUT=$$(diff -u expected_tasks.ndjson $<); \ + echo "$$DIFF_OUTPUT"; \ + exit 1; \ + fi + diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md new file mode 100644 index 0000000..f670c54 --- /dev/null +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md @@ -0,0 +1,6 @@ + +# bla bla + +Lorem ipsum dolor sit amet, consectetur adipisicing elit. + +- [ ] write feed the cat process MR242 JIRA:OMD-127 [id:: 16a1d4bb-fec6-416b-be70-6df438a6f44e] diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson new file mode 100644 index 0000000..5c181d5 --- /dev/null +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson @@ -0,0 +1,16 @@ +{ + "description": "write feed the cat process MR242 JIRA:OMD-127", + "status": "pending", + "uuid": "16a1d4bb-fec6-416b-be70-6df438a6f44e", + "annotations": [ + { + "description": "Source: existing_markdown_file.md" + }, + { + "description": "https://gitlab.tech.orange/OrangeMoney/Retailer/pilotagedistri/business-server/-/merge_requests/242" + }, + { + "description": "https://jira.tech.orange/browse/OMD-127" + } + ] +} From b488b732a1f27f2acdcb57c9918e7690fe150654 Mon Sep 17 00:00:00 2001 From: Nicolas Bossard Date: Wed, 30 Apr 2025 22:19:11 +0200 Subject: [PATCH 2/6] adding functions to replace markers --- mtt_md_to_taskwarrior.sh | 62 ++++++++++++------- .../existing_markdown_file.md | 2 +- .../expected_tasks.ndjson | 9 ++- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/mtt_md_to_taskwarrior.sh b/mtt_md_to_taskwarrior.sh index 64c6a13..6e6ac13 100755 --- a/mtt_md_to_taskwarrior.sh +++ b/mtt_md_to_taskwarrior.sh @@ -48,6 +48,32 @@ format_date() { fi } +# Function to add an annotation with proper JSON formatting +# to global variable "annotations" +add_annotation() { + local url="$1" + if [ -n "$annotations" ]; then + annotations+="," + fi + annotations+="{\"description\":\"$url\"}" +} + +# Function to extract and handle different types of references +# that can be found in description and add them to annotations global var +handle_reference() { + local type="$1" # E.g : "Merge request" + local pattern_regex="$2" # E.g.: "MR[0-9]+" + local prefix="$3" # E.g. : "MR" + local url_template="$4" + + local reference=$(echo "$line" | grep -o -E "$pattern_regex" | head -n 1) + if [ -n "$reference" ]; then + echo "found $type: $reference" + local clean_ref=${reference#$prefix} # Remove prefix + add_annotation "$(printf "$url_template" "$clean_ref")" + fi +} + # Set defaults from environment variables or fallback values file_mask="${OE_MASK:-*.md}" project_name="${OE_PROJECT:-}" @@ -190,28 +216,18 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while echo "converted priority : $priority" fi - ## extract the merge request number and convert it to annotation - ## e.g. : "MR414" in description should be converted to annotation : - ## {"entry":"20120110T234559Z","description":"https://gitlab.tech.orange/OrangeMoney/Retailer/pilotagedistri/business-server/-/merge_requests/414"} - mr_number=$(echo "$line" | grep -o 'MR[0-9]\+' | head -n 1) - mr_annotations="" - if [ -n "$mr_number" ]; then - echo "found merge request: $mr_number" - mr_num=${mr_number#MR} # Remove 'MR' prefix to get just the number - mr_annotations="{\"description\":\"https://gitlab.tech.orange/OrangeMoney/Retailer/pilotagedistri/business-server/-/merge_requests/$mr_num\"}" - fi - # Handle JIRA ticket - jira_ticket=$(echo "$line" | grep -o -E '(JIRA:)?OMD-[0-9]+' | head -n 1) - if [ -n "$jira_ticket" ]; then - echo "found JIRA ticket: $jira_ticket" - # Remove JIRA: prefix if present - clean_ticket=${jira_ticket#JIRA:} - if [ -n "$mr_annotations" ]; then - mr_annotations+="," - fi - mr_annotations+="{\"description\":\"https://jira.tech.orange/browse/$clean_ticket\"}" - fi + # Initialize annotations string + annotations="" + + + # Handle different reference types + # Handle MR242 + handle_reference "merge request" "MR[0-9]+" "MR" "https://gitlab.tech.orange/OrangeMoney/Retailer/paymetrics/monorepo/-/merge_requests/%s" + # Handle JIRA:OMD-127 + handle_reference "JIRA ticket" "(JIRA:)?OMD-[0-9]+" "JIRA:" "JIRA: %s" + # Handle Git:feat/improve_makefile_auth + handle_reference "Git branch" "Git:[^[:space:]]+" "Git:" "Git: %s" # Extract all @ tags # CONFLICT @ concept does not exist in taskwarrior, doing nothing for now @@ -258,8 +274,8 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while # Add all annotations (source, MR, and JIRA) json+=",\"annotations\":[{\"description\":\"Source: $abs_file_path\"}" - if [ -n "$mr_annotations" ]; then - json+=",$mr_annotations" + if [ -n "$annotations" ]; then + json+=",$annotations" fi json+="]" json+="}" diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md index f670c54..dcf15bd 100644 --- a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/existing_markdown_file.md @@ -3,4 +3,4 @@ Lorem ipsum dolor sit amet, consectetur adipisicing elit. -- [ ] write feed the cat process MR242 JIRA:OMD-127 [id:: 16a1d4bb-fec6-416b-be70-6df438a6f44e] +- [ ] write feed the cat process MR242 JIRA:OMD-127 Git:feat/improve_makefile_auth [id:: 16a1d4bb-fec6-416b-be70-6df438a6f44e] diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson index 5c181d5..42cbf84 100644 --- a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson @@ -1,5 +1,5 @@ { - "description": "write feed the cat process MR242 JIRA:OMD-127", + "description": "write feed the cat process MR242 JIRA:OMD-127 Git:feat/improve_makefile_auth", "status": "pending", "uuid": "16a1d4bb-fec6-416b-be70-6df438a6f44e", "annotations": [ @@ -7,10 +7,13 @@ "description": "Source: existing_markdown_file.md" }, { - "description": "https://gitlab.tech.orange/OrangeMoney/Retailer/pilotagedistri/business-server/-/merge_requests/242" + "description": "https://gitlab.tech.orange/OrangeMoney/Retailer/paymetrics/monorepo/-/merge_requests/242" }, { - "description": "https://jira.tech.orange/browse/OMD-127" + "description": "JIRA: OMD-127" + }, + { + "description": "Git: feat/improve_makefile_auth" } ] } From fc98617a811abb6e58d66b67e3220e4dc1be588d Mon Sep 17 00:00:00 2001 From: Nicolas Bossard Date: Wed, 30 Apr 2025 22:39:58 +0200 Subject: [PATCH 3/6] moving to config file --- config.json | 23 +++++++++++++++++++++++ mtt_md_to_taskwarrior.sh | 27 ++++++++++++++++++++------- 2 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 config.json diff --git a/config.json b/config.json new file mode 100644 index 0000000..8ec6b3a --- /dev/null +++ b/config.json @@ -0,0 +1,23 @@ +{ + "references": [ + { + "type": "merge request", + "pattern": "MR[0-9]+", + "prefix": "MR", + "url_template": "https://gitlab.tech.orange/OrangeMoney/Retailer/paymetrics/monorepo/-/merge_requests/%s" + }, + { + "type": "JIRA ticket", + "pattern": "(JIRA:)?OMD-[0-9]+", + "prefix": "JIRA:", + "url_template": "JIRA: %s" + }, + { + "type": "Git branch", + "pattern": "Git:[^[:space:]]+", + "prefix": "Git:", + "url_template": "Git: %s" + } + ] +} + diff --git a/mtt_md_to_taskwarrior.sh b/mtt_md_to_taskwarrior.sh index 6e6ac13..c3d2f77 100755 --- a/mtt_md_to_taskwarrior.sh +++ b/mtt_md_to_taskwarrior.sh @@ -5,6 +5,12 @@ echo echo "mtt - ------------ starting markdown tasks export -----------------" echo # +# Check for required dependencies +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed. Please install jq first." + exit 1 +fi + # Load environment variables from .env file if it exists if [ -f .env ]; then echo "Loading configuration from .env file" @@ -221,13 +227,20 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while annotations="" - # Handle different reference types - # Handle MR242 - handle_reference "merge request" "MR[0-9]+" "MR" "https://gitlab.tech.orange/OrangeMoney/Retailer/paymetrics/monorepo/-/merge_requests/%s" - # Handle JIRA:OMD-127 - handle_reference "JIRA ticket" "(JIRA:)?OMD-[0-9]+" "JIRA:" "JIRA: %s" - # Handle Git:feat/improve_makefile_auth - handle_reference "Git branch" "Git:[^[:space:]]+" "Git:" "Git: %s" + # Handle different reference types from config file + if [ ! -f "config.json" ]; then + echo "Warning: config.json not found, skipping reference handling" + else + while IFS= read -r ref_config; do + # Parse the JSON config line into variables + type=$(echo "$ref_config" | jq -r '.type') + pattern=$(echo "$ref_config" | jq -r '.pattern') + prefix=$(echo "$ref_config" | jq -r '.prefix') + url_template=$(echo "$ref_config" | jq -r '.url_template') + + handle_reference "$type" "$pattern" "$prefix" "$url_template" + done < <(jq -c '.references[]' config.json) + fi # Extract all @ tags # CONFLICT @ concept does not exist in taskwarrior, doing nothing for now From 1d63a4e80f872a8a2a3567e0fd40115fabfd5876 Mon Sep 17 00:00:00 2001 From: Nicolas Bossard Date: Wed, 30 Apr 2025 23:03:55 +0200 Subject: [PATCH 4/6] fix: adding missing tmp folder --- tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/tmp/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/tmp/.gitkeep diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/tmp/.gitkeep b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/tmp/.gitkeep new file mode 100644 index 0000000..e69de29 From e68ff022ccfd79f3efc1a685003a6a3d5eb0184e Mon Sep 17 00:00:00 2001 From: Nicolas Bossard Date: Wed, 30 Apr 2025 23:18:02 +0200 Subject: [PATCH 5/6] adding configurable config file --- mtt_md_to_taskwarrior.sh | 21 ++++++++++++----- .../config.json | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/config.json diff --git a/mtt_md_to_taskwarrior.sh b/mtt_md_to_taskwarrior.sh index c3d2f77..302b0eb 100755 --- a/mtt_md_to_taskwarrior.sh +++ b/mtt_md_to_taskwarrior.sh @@ -33,10 +33,10 @@ show_help() { echo " --help Show this help message" echo " --mask PATTERN File pattern to search (default: *.md)" echo " --project NAME Assign tasks to a specific project" + echo " --config PATH Path to references config file (default: config.json)" echo - echo "Environment Variables:" - echo " OE_MASK Alternative to --mask (command line takes precedence)" echo " OE_PROJECT Alternative to --project (command line takes precedence)" + echo " OE_CONFIG Alternative to --config (command line takes precedence)" exit 0 } @@ -83,6 +83,7 @@ handle_reference() { # Set defaults from environment variables or fallback values file_mask="${OE_MASK:-*.md}" project_name="${OE_PROJECT:-}" +config_file="${OE_CONFIG:-config.json}" # Parse command line arguments while [[ "$#" -gt 0 ]]; do @@ -97,8 +98,6 @@ while [[ "$#" -gt 0 ]]; do show_help exit 1 fi - ;; - --project) shift if [[ -n "$1" ]]; then project_name="$1" @@ -107,6 +106,15 @@ while [[ "$#" -gt 0 ]]; do show_help fi ;; + --config) + shift + if [[ -n "$1" ]]; then + config_file="$1" + else + echo "Error: --config requires a path" + show_help + fi + ;; *) echo "Unknown parameter: $1"; show_help ;; esac shift @@ -117,6 +125,7 @@ echo "Current configuration:" echo "~~~~~~~~~~~~~~~~~~~~" echo "File mask: $file_mask" echo "Project: ${project_name:-}" +echo "Config file: $config_file" echo "Output file: tasks.ndjson" echo "~~~~~~~~~~~~~~~~~~~~" echo @@ -228,7 +237,7 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while # Handle different reference types from config file - if [ ! -f "config.json" ]; then + if [ ! -f "$config_file" ]; then echo "Warning: config.json not found, skipping reference handling" else while IFS= read -r ref_config; do @@ -239,7 +248,7 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while url_template=$(echo "$ref_config" | jq -r '.url_template') handle_reference "$type" "$pattern" "$prefix" "$url_template" - done < <(jq -c '.references[]' config.json) + done < <(jq -c '.references[]' "$config_file") fi # Extract all @ tags diff --git a/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/config.json b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/config.json new file mode 100644 index 0000000..8ec6b3a --- /dev/null +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/config.json @@ -0,0 +1,23 @@ +{ + "references": [ + { + "type": "merge request", + "pattern": "MR[0-9]+", + "prefix": "MR", + "url_template": "https://gitlab.tech.orange/OrangeMoney/Retailer/paymetrics/monorepo/-/merge_requests/%s" + }, + { + "type": "JIRA ticket", + "pattern": "(JIRA:)?OMD-[0-9]+", + "prefix": "JIRA:", + "url_template": "JIRA: %s" + }, + { + "type": "Git branch", + "pattern": "Git:[^[:space:]]+", + "prefix": "Git:", + "url_template": "Git: %s" + } + ] +} + From f325059585dd661f7cdd85705f587aa395678d86 Mon Sep 17 00:00:00 2001 From: Nicolas Bossard Date: Fri, 2 May 2025 09:04:23 +0200 Subject: [PATCH 6/6] docs: improve changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e39855d..e60f70c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Configurable support of keywords like "MR242" or "JIRA:OMD-3456" to be converted to annotations. Can later be used with taskopen. + ## [0.0.6] - 2024-05-02 ### Added @@ -14,7 +16,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added script directory detection to make installation location independent - Added check that required programs are well available - Added tests on tags good working -- Begin support of keywords like "MR242" or "JIRA:OMD-3456" to be converted to annotations with URL ### Changed