diff --git a/CHANGELOG.md b/CHANGELOG.md index c6dc788..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 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 13f261e..302b0eb 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" @@ -27,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 } @@ -48,9 +54,36 @@ 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:-}" +config_file="${OE_CONFIG:-config.json}" # Parse command line arguments while [[ "$#" -gt 0 ]]; do @@ -65,8 +98,6 @@ while [[ "$#" -gt 0 ]]; do show_help exit 1 fi - ;; - --project) shift if [[ -n "$1" ]]; then project_name="$1" @@ -75,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 @@ -85,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 @@ -190,6 +231,26 @@ rg --no-heading --line-number --with-filename "^- \\[ \\] " $file_mask | while echo "converted priority : $priority" fi + + # Initialize annotations string + annotations="" + + + # Handle different reference types from config file + if [ ! -f "$config_file" ]; 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_file") + 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 +293,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 "$annotations" ]; then + json+=",$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/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" + } + ] +} + 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..dcf15bd --- /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 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 new file mode 100644 index 0000000..42cbf84 --- /dev/null +++ b/tests/test_md_to_taskwarrior_5_with_MR_and_JIRA/expected_tasks.ndjson @@ -0,0 +1,19 @@ +{ + "description": "write feed the cat process MR242 JIRA:OMD-127 Git:feat/improve_makefile_auth", + "status": "pending", + "uuid": "16a1d4bb-fec6-416b-be70-6df438a6f44e", + "annotations": [ + { + "description": "Source: existing_markdown_file.md" + }, + { + "description": "https://gitlab.tech.orange/OrangeMoney/Retailer/paymetrics/monorepo/-/merge_requests/242" + }, + { + "description": "JIRA: OMD-127" + }, + { + "description": "Git: feat/improve_makefile_auth" + } + ] +} 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