diff --git a/.devcontainer/apt-install/install.sh b/.devcontainer/apt-install/install.sh index 13e8524..c536388 100644 --- a/.devcontainer/apt-install/install.sh +++ b/.devcontainer/apt-install/install.sh @@ -1,11 +1,19 @@ #!/bin/sh -apt-get update -y +set -e # Exit on error -# Basic, commonly needed, dependencies of Ruby & JRuby projects +# Install basic development dependencies for Ruby & JRuby projects +apt-get update -y apt-get install -y direnv default-jdk git zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev -# Support for PostgreSQL +# Support for PostgreSQL (commented out by default) # apt-get install -y postgresql libpq-dev +# NOTE: Tree-sitter setup is NOT done here because the workspace is not mounted yet +# during the devcontainer build phase. Tree-sitter setup happens in postCreateCommand +# after the workspace is mounted. See devcontainer.json for details. +# This gem needs ALL grammars for fenced code block merging (handled by setup-tree-sitter.sh). + +echo "Basic apt packages installed. Tree-sitter will be set up after workspace mount." + # Adds the direnv setup script to ~/.bashrc file (at the end) echo 'eval "$(direnv hook bash)"' >> ~/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4129e09..05c1f4f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,7 +14,9 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bundle update --bundler", + // First setup tree-sitter (auto-detects sudo requirement), then run bundle as the regular user + // Use ${containerWorkspaceFolder} to get the actual workspace path in the container + "postCreateCommand": "bash ${containerWorkspaceFolder}/.github/scripts/ubuntu/setup-tree-sitter.sh --workspace=${containerWorkspaceFolder} && bundle update --bundler", // Configure tool-specific properties. "customizations" : { diff --git a/.envrc b/.envrc index fa9ee5b..f489a3d 100644 --- a/.envrc +++ b/.envrc @@ -41,6 +41,7 @@ export K_SOUP_COV_OPEN_BIN=false # Means don't try to open coverage results in b export MAX_ROWS=1 # Setting for simplecov-console gem for tty output, limits to the worst N rows of bad coverage export KETTLE_TEST_SILENT=true export KETTLE_DEV_DEBUG=false +export KETTLE_RB_DEV=false # Internal Debugging Controls @@ -57,6 +58,31 @@ export RUBOCOP_LTS_LOCAL=false export OPENCOLLECTIVE_HANDLE=kettle-rb export FUNDING_ORG=kettle-rb +# tree_haver dependencies (SONAME form) +export TREE_SITTER_RUNTIME_LIB=/home/linuxbrew/.linuxbrew/Cellar/tree-sitter/0.26.3/lib/libtree-sitter.so +# Run vendor/setup-java-tree-sitter.sh to download JARs +export TREE_SITTER_JAVA_JARS_DIR=/home/pboling/src/kettle-rb/ast-merge/vendor/tree_haver/vendor/jars +# JVM options for java-tree-sitter: +# - --enable-native-access: Required for Java Foreign Function API +# - -Djava.library.path: Path to libtree-sitter.so for JNI loading +export JAVA_OPTS="--enable-native-access=ALL-UNNAMED -Djava.library.path=/home/linuxbrew/.linuxbrew/Cellar/tree-sitter/0.26.3/lib" +export JRUBY_OPTS="-J--enable-native-access=ALL-UNNAMED -J-Djava.library.path=/home/linuxbrew/.linuxbrew/Cellar/tree-sitter/0.26.3/lib" +# If Ruby/JRuby cannot find libraries at runtime, you may need to export: +# LD_LIBRARY_PATH is needed for java-tree-sitter to find libtree-sitter.so at runtime +# export LD_LIBRARY_PATH=/usr/local/lib:/usr/lib64:/usr/lib +export LD_LIBRARY_PATH="/home/linuxbrew/.linuxbrew/Cellar/tree-sitter/0.26.3/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + +# Example language library path: +export TREE_SITTER_TOML_PATH=/home/pboling/.local/share/mise/installs/lua/5.4.8/luarocks/lib/luarocks/rocks-5.4/tree-sitter-toml/0.0.31-1/parser/toml.so +export TREE_SITTER_JSON_PATH=/home/pboling/.local/share/mise/installs/lua/5.4.8/luarocks/lib/luarocks/rocks-5.4/tree-sitter-json/0.0.29-1/parser/json.so +export TREE_SITTER_JSONC_PATH=/home/pboling/.local/share/mise/installs/lua/5.4.8/luarocks/lib/luarocks/rocks-5.4/tree-sitter-jsonc/0.0.29-1/parser/jsonc.so +# Luarocks version (incompatible with MRI backend): +export TREE_SITTER_BASH_PATH=/home/pboling/.local/share/mise/installs/lua/5.4.8/luarocks/lib/luarocks/rocks-5.4/tree-sitter-bash/0.0.49-1/parser/bash.so +# npm version (N-API, so WILL NOT WORK with ruby_tree_sitter): +# export TREE_SITTER_BASH_PATH=/var/home/pboling/.local/share/mise/installs/node/23.11.0/lib/node_modules/tree-sitter-bash/prebuilds/linux-x64/tree-sitter-bash.node +# Compiled from source (compatible with ruby_tree_sitter) +# export TREE_SITTER_BASH_PATH=/tmp/tree-sitter-bash/libtree-sitter-bash.so + # .env would override anything in this file, if `dotenv` is uncommented below. # .env is a DOCKER standard, and if we use it, it would be in deployed, or DOCKER, environments, diff --git a/.github/COPILOT_INSTRUCTIONS.md b/.github/COPILOT_INSTRUCTIONS.md new file mode 100644 index 0000000..6d86285 --- /dev/null +++ b/.github/COPILOT_INSTRUCTIONS.md @@ -0,0 +1,344 @@ +# GitHub Copilot Instructions for ast-merge + +This document contains important information for AI assistants working on this codebase. + +## Tool Usage Preferences + +### Prefer Internal Tools Over Terminal Commands + +**IMPORTANT**: Copilot cannot see terminal output. Every terminal command requires the user to manually copy/paste the output back to the chat. This is slow and frustrating. + +✅ **PREFERRED** - Use internal tools: +- `grep_search` instead of `grep` command +- `file_search` instead of `find` command +- `read_file` instead of `cat` command +- `list_dir` instead of `ls` command +- `replace_string_in_file` or `create_file` instead of `sed` / manual editing + +❌ **AVOID** when possible: +- `run_in_terminal` for information gathering +- Running grep/find/cat in terminal + +Only use terminal for: +- Running tests (`bundle exec rspec`) +- Installing dependencies (`bundle install`) +- Git operations that require interaction +- Commands that actually need to execute (not just gather info) + +## Search Tool Limitations + +### grep_search and Nested Git Projects + +**CRITICAL**: The `vendor/*` directories in this workspace are **nested git projects** (they have their own `.git/` directory, separated from the parent by gitignore patterns). The `grep_search` tool **CANNOT search inside nested git projects** - it only searches the main workspace. + +To search inside vendor gems: +1. Use `read_file` to read specific files directly (this always works) +2. Use `list_dir` to explore the directory structure +3. Do NOT rely on `grep_search` with `includePattern: "vendor/**"` - it will return no results + +**Working approach for finding code in vendor gems:** +``` +# Step 1: List the directory structure +list_dir("/home/pboling/src/kettle-rb/ast-merge/vendor/markdown-merge/lib/markdown/merge") + +# Step 2: Read specific files you need to search +read_file("/home/pboling/src/kettle-rb/ast-merge/vendor/markdown-merge/lib/markdown/merge/file_analysis.rb", 0, 100) + +# Step 3: If looking for a specific method, read more of the file or multiple files +``` + +### grep_search includePattern (for non-nested-git directories) + +**IMPORTANT**: The `includePattern` parameter uses glob patterns relative to the workspace root. + +✅ **WORKS** - Use these patterns: +``` +# Search recursively within a directory (use ** for recursive) +includePattern: "vendor/**" # All files under vendor/ +includePattern: "vendor/kettle-dev/**" # All files under vendor/kettle-dev/ + +# Search a specific file +includePattern: "vendor/prism-merge/README.md" +includePattern: "lib/ast/merge/freezable.rb" + +# Search files matching a pattern in a specific directory +includePattern: "spec/**" # All spec files recursively +``` + +❌ **DOES NOT WORK** - Avoid these: +``` +# The | character does NOT work for alternation in includePattern +includePattern: "vendor/prism-merge/**|vendor/kettle-dev/**" + +# Cannot use ** in the middle of a path with file extension +includePattern: "vendor/**/spec/**/*.rb" # Too complex, may fail +``` + +**Key insights:** +- `vendor/**` searches ALL files recursively under vendor/ (including subfolders) +- Without `includePattern`, `grep_search` searches the ENTIRE workspace (root project only, may miss vendor submodules) +- For vendor gems, always use `includePattern: "vendor/**"` or `includePattern: "vendor/gem-name/**"` +- To search multiple specific locations, make separate `grep_search` calls + +### replace_string_in_file and Unicode Characters + +**IMPORTANT**: The `replace_string_in_file` tool can fail silently when files contain special Unicode characters like: +- Curly apostrophes (`'` U+2019 instead of `'`) +- Em-dashes (`—` U+2014 instead of `-`) +- Emoji (🔧, 🎨, etc.) + +When `replace_string_in_file` fails with "Could not find matching text": +1. The file likely contains Unicode characters that don't match what you're sending +2. Try using a smaller, more unique portion of the text +3. Avoid including lines with emojis or special punctuation in your oldString +4. Use `read_file` to see the exact content, but be aware the display may normalize characters + +## Project Structure + +- `lib/ast/merge/` - Base library classes (ast-merge gem) +- `vendor/prism-merge/` - Ruby/Prism-specific merge implementation +- `vendor/*/` - Other format-specific merge implementations (markly-merge, json-merge, etc.) + +## API Conventions + +### Forward Compatibility with **options + +**CRITICAL DESIGN PRINCIPLE**: All constructors and public API methods that accept keyword arguments MUST include `**options` (or similar catch-all) as the final parameter to capture unknown options. + +✅ **CORRECT**: +```ruby +def initialize(source, freeze_token: DEFAULT, signature_generator: nil, **options) + @source = source + @freeze_token = freeze_token + @signature_generator = signature_generator + # **options captures future parameters for forward compatibility +end +``` + +❌ **WRONG**: +```ruby +def initialize(source, freeze_token: DEFAULT, signature_generator: nil) + # This breaks when new parameters are added to the base class +end +``` + +**Why**: When `SmartMergerBase` adds new standard options (like `node_typing`, `regions`, etc.), all `FileAnalysis` classes automatically support them without code changes. Without `**options`, every FileAnalysis would need updating whenever a new option is added. + +**Applies to**: +- `FileAnalysis#initialize` in all gems +- `SmartMerger#initialize` in all gems +- Any method that accepts a variable set of options + +### SmartMergerBase API +- `merge` - Returns a **String** (the merged content) +- `merge_result` - Returns a **MergeResult** object +- `to_s` on MergeResult returns the merged content as a string + +### Comment Classes +- `Ast::Merge::Comment::*` - Generic, language-agnostic comment classes +- `Prism::Merge::Comment::*` - Ruby-specific comment classes with magic comment detection + +### Naming Conventions +- File paths must match class paths (Ruby convention) +- Example: `Ast::Merge::Comment::Line` → `lib/ast/merge/comment/line.rb` + +## Architecture Notes + +### prism-merge (as of December 2024) +- Uses section-based merging with recursive body merging +- Does NOT use FileAligner or ConflictResolver (these were removed as vestigial) +- SmartMerger handles all merge logic directly +- Comment-only files are handled via `Ast::Merge::Text::SmartMerger` + +## Loading Vendor Gems in Scripts + +**IMPORTANT**: The approach depends on whether you're using the project's Gemfile or need standalone execution. + +### For Scripts Using Project Gemfile (bundler/setup) + +✅ **Use bundler/setup when**: +- The script runs within the project context +- The Gemfile already specifies all needed gems with `path:` options +- You want to use the exact versions locked in Gemfile.lock + +```ruby +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "prism/merge" + +# Now you can use Prism::Merge classes +``` + +### For Standalone Scripts with Local Paths (bundler/inline) + +✅ **Use bundler/inline when**: +- Creating standalone scripts in `bin/` that need specific gem paths +- Testing with fixture gems or specific local paths +- The script needs to specify its own dependencies independent of the project Gemfile +- You need to load gems from vendor directories not in the main Gemfile + +```ruby +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/inline" + +gemfile do + source "https://rubygems.org" + gem "ast-merge", path: File.expand_path("..", __dir__) + gem "tree_haver", path: File.expand_path("../vendor/tree_haver", __dir__) + gem "markdown-merge", path: File.expand_path("../vendor/markdown-merge", __dir__) +end + +# Now gems are loaded and ready to use +require "markdown/merge" +``` + +**Why bundler/inline for standalone scripts:** +1. The gemfile block creates an inline Gemfile with specified paths +2. Bundler resolves dependencies and configures load paths +3. Scripts become self-contained and portable +4. No need to modify the project's main Gemfile + +**Common pitfall with bundler/inline:** +- If a gem in your inline gemfile has unresolved dependencies, bundler will try to fetch them +- Solution: Only include gems you actually need, or ensure all transitive dependencies are available + +❌ **BROKEN** - These do NOT work: +```ruby +# This doesn't load the gem properly: +require_relative "lib/prism/merge" + +# This doesn't set up the load path: +require "prism/merge" # without bundler/setup or bundler/inline first +``` + +## Testing + +### kettle-test RSpec Helpers + +**IMPORTANT**: All spec files load `require "kettle/test/rspec"` which provides extensive RSpec helpers and configuration from the kettle-test gem. DO NOT recreate these helpers - they already exist. + +**Environment Variable Helpers** (from `rspec-stubbed_env` gem): +- `stub_env(hash)` - Temporarily set environment variables in a block +- `hide_env(*keys)` - Temporarily hide environment variables + +**Example usage**: +They are not used with blocks, but can be used like this: +```ruby +before do + stub_env("MY_ENV_VAR" => "Bla Blah Blu") +end +it "should see MY_ENV_VAR" do + # code that reads ENV["MY_ENV_VAR"] +end + +# hide_env("HOME", "USER") +# is used the same way, but hides the variable so it acts as it if isn't set at all. +``` + +**Other Helpers** (loaded by kettle-test): +- `block_is_expected` - Enhanced block expectations (from `rspec-block_is_expected`) +- `capture` - Capture output during tests (from `silent_stream`) +- Timecop integration for time manipulation + +**Where these come from**: +- External gems loaded by `kettle/test/external.rb` in the kettle-test gem +- `rspec/stubbed_env` - Provides `stub_env` and `hide_env` +- `rspec/block_is_expected` - Enhanced block expectations +- `silent_stream` - Output suppression +- `timecop/rspec` - Time travel for tests + +**Other Helpers** (loaded by kettle-test): +- `block_is_expected` - Enhanced block expectations (from `rspec-block_is_expected`) +- `capture` - Capture output during tests (from `silent_stream`) +- Timecop integration for time manipulation + +**Where these come from**: +- External gems loaded by `kettle/test/external.rb` in the kettle-test gem +- `rspec/stubbed_env` - Provides `stub_env` and `hide_env` +- `rspec/block_is_expected` - Enhanced block expectations +- `silent_stream` - Output suppression +- `timecop/rspec` - Time travel for tests + +### Running Tests + +Run tests from the appropriate directory: +```bash +# ast-merge tests +cd /var/home/pboling/src/kettle-rb/ast-merge +bundle exec rspec spec/ + +# prism-merge tests +cd /var/home/pboling/src/kettle-rb/ast-merge/vendor/prism-merge +bundle exec rspec spec/ +``` + +### Coverage Reports + +To generate a coverage report for any vendor gem: +```bash +cd /var/home/pboling/src/kettle-rb/ast-merge/vendor/prism-merge # or other vendor gem +bin/rake coverage && bin/kettle-soup-cover -d +``` + +This runs tests with coverage instrumentation and generates detailed coverage reports in the `coverage/` directory. + +## Common Pitfalls + +1. **NEVER add backward compatibility** - The maintainer explicitly prohibits backward compatibility shims, aliases, or deprecation layers. Make clean breaks. +2. **Magic comments** - Ruby-specific, belong in prism-merge not ast-merge +3. **`content_string` is legacy** - Use `to_s` instead +4. **`merged_source` doesn't exist** - `merge` returns a String directly + +## Terminal Command Restrictions + +### Terminal Session Management + +**How `run_in_terminal` works**: +- The tool sends commands to a **single persistent Copilot terminal** +- Use `isBackground=false` for `run_in_terminal`. Sometimes it works, but if it fails/hangs, use the file redirection method, and then read back with `read_file` tool. +- Commands run in sequence in the same terminal session +- Environment variables and working directory persist between calls +- The first command in a session either does not run at all, or runs before the shell initialization (direnv, motd, etc.) so it should always be a noop, like `true`. + +**When things go wrong**: +- If output shows only shell banner/motd without command results, the command most likely worked, but the tool has lost the ability to see terminal output. This happens FREQUENTLY. +- EVERY TIME you do not see output, STOP and confirm output status with the user, or switch immediately to file redirection, and read the file back with `read_file` tool. +- **ALWAYS use project's `tmp/` directory for temporary files** - NEVER use `/tmp` or other system directories +- Solution: Ask the user to share the output they see. + +**Best practices**: +1. **Combine related commands** - Use `&&` to chain commands: + ```bash + cd /path && bundle exec rspec spec/some_spec.rb + ``` +2. **Use `get_terminal_output`** - Check output of background processes +3. **Prefer internal tools** - Use `grep_search`, `read_file`, `list_dir` instead of terminal for information gathering + +### NEVER Pipe Test Commands Through head/tail + +**CRITICAL**: NEVER use `head`, `tail`, or any output truncation with test commands (`rspec`, `rake`, `bundle exec rspec`, etc.). + +❌ **ABSOLUTELY FORBIDDEN**: +```bash +bundle exec rspec 2>&1 | tail -50 +bin/rake coverage 2>&1 | head -100 +bin/rspec | tail -200 +``` + +✅ **CORRECT** - Run commands without truncation: +```bash +bundle exec rspec +bin/rake coverage +bin/rspec +``` + +**Why**: +- You cannot predict how much output a test run will produce +- Your predictions are ALWAYS wrong +- You cannot see terminal output anyway - the user will copy relevant portions for you +- Truncating output often hides the actual errors or relevant information +- The user knows what's important and will share it with you diff --git a/.github/scripts/ubuntu/setup-tree-sitter.sh b/.github/scripts/ubuntu/setup-tree-sitter.sh new file mode 100755 index 0000000..69dce30 --- /dev/null +++ b/.github/scripts/ubuntu/setup-tree-sitter.sh @@ -0,0 +1,234 @@ +#!/bin/bash +set -e + +# Setup script for tree-sitter dependencies (Ubuntu/Debian) +# Works for both GitHub Actions and devcontainer environments +# +# Dual-Environment Design: +# - GitHub Actions: Runs as non-root user, auto-detects need for sudo +# - Devcontainer: Can run as root (apt-install feature) or non-root (postCreateCommand) +# - Auto-detection: Checks if running as root (id -u = 0), uses sudo if non-root +# +# This script installs ALL tree-sitter grammars for integration testing +# +# Options: +# --sudo: Force use of sudo (optional, auto-detected by default) +# --cli: Install tree-sitter-cli via npm (optional) +# --build: Build and install the tree-sitter C runtime from source when distro packages are missing (optional) +# --workspace PATH: Workspace root path for informational/debugging purposes only (defaults to /workspaces/tree_haver) + +SUDO="" +INSTALL_CLI=false +BUILD_FROM_SOURCE=false +WORKSPACE_ROOT="/workspaces/markly-merge" + +# Parse arguments properly using while loop +while [[ $# -gt 0 ]]; do + case $1 in + --sudo) + SUDO="sudo" + shift + ;; + --cli) + INSTALL_CLI=true + shift + ;; + --build) + BUILD_FROM_SOURCE=true + shift + ;; + --workspace) + WORKSPACE_ROOT="$2" + shift 2 + ;; + --workspace=*) + WORKSPACE_ROOT="${1#*=}" + shift + ;; + *) + echo "Unknown option: $1" >&2 + shift + ;; + esac +done + +# Auto-detect if we need sudo (running as non-root) +if [ -z "$SUDO" ] && [ "$(id -u)" -ne 0 ]; then + SUDO="sudo" +fi + +echo "Configuration:" +echo " Workspace root: $WORKSPACE_ROOT (informational only)" +echo " Using sudo: $([ -n "$SUDO" ] && echo "yes" || echo "no")" +echo " Install CLI: $INSTALL_CLI" +echo " Build from source: $BUILD_FROM_SOURCE" +echo "" + +have_cmd() { command -v "$1" >/dev/null 2>&1; } + +have_tree_sitter() { + [ -f /usr/include/tree-sitter/api.h ] && return 0 + [ -f /usr/local/include/tree-sitter/api.h ] && return 0 + [ -f /usr/local/include/tree-sitter/lib/include/api.h ] && return 0 + ldconfig -p 2>/dev/null | grep -q libtree-sitter && return 0 || return 1 +} + +install_tree_sitter_from_source() { + echo "[ubuntu] Attempting to build and install tree-sitter from source..." + tmpdir=$(mktemp -d /tmp/tree-sitter-src-XXXX) + trap 'rm -rf "$tmpdir"' EXIT + git clone --depth 1 https://github.com/tree-sitter/tree-sitter.git "$tmpdir" || return 1 + pushd "$tmpdir" >/dev/null || return 1 + if ! make; then + echo "[ubuntu] ERROR: 'make' failed while building tree-sitter" >&2 + popd >/dev/null + return 1 + fi + + $SUDO mkdir -p /usr/local/include/tree-sitter + $SUDO cp -r lib/include/* /usr/local/include/tree-sitter/ || true + $SUDO cp -a lib/libtree-sitter.* /usr/local/lib/ 2>/dev/null || true + if have_cmd ldconfig; then + $SUDO ldconfig || true + fi + + popd >/dev/null + echo "[ubuntu] tree-sitter built and installed to /usr/local (headers + libs)." + return 0 +} + +echo "Installing tree-sitter system library and dependencies..." +$SUDO apt-get update -y +# libtree-sitter-dev is optional when building from source via --build +if ! $SUDO apt-get install -y \ + build-essential \ + pkg-config \ + $( [ "$BUILD_FROM_SOURCE" = false ] && echo "libtree-sitter-dev" ) \ + wget \ + gcc \ + g++ \ + make \ + zlib1g-dev \ + libssl-dev \ + libreadline-dev \ + libyaml-dev \ + libxml2-dev \ + libxslt1-dev \ + libcurl4-openssl-dev \ + software-properties-common \ + libffi-dev; then + echo "ERROR: apt-get failed to install required packages." + echo "Please check your network, package sources, and re-run this script." + exit 1 +fi + +# If the user requested a source-build, skip installing libtree-sitter-dev +if [ "$BUILD_FROM_SOURCE" = true ]; then + echo "[ubuntu] --build specified; skipping distro package 'libtree-sitter-dev' and building tree-sitter from source." +fi + +# Ensure tree-sitter is available; if not, attempt to build from source +if ! have_tree_sitter; then + if [ "$BUILD_FROM_SOURCE" = true ]; then + echo "[ubuntu] tree-sitter not found in system paths; attempting to build from source as requested (--build)." + if ! install_tree_sitter_from_source; then + echo "[ubuntu] ERROR: Failed to provide tree-sitter runtime/library. Aborting." >&2 + exit 1 + fi + else + echo "[ubuntu] ERROR: tree-sitter runtime (headers/libs) not found." + echo "Install the appropriate distro package (e.g., libtree-sitter-dev) or re-run this script with --build to compile from source." + exit 1 + fi +fi + +# Install tree-sitter CLI via npm (optional) +if [ "$INSTALL_CLI" = true ]; then + echo "Installing tree-sitter-cli via npm..." + $SUDO npm install -g tree-sitter-cli +else + echo "Skipping tree-sitter-cli installation (use --cli flag to install)" +fi + +# Install all tree-sitter grammars for integration testing +GRAMMARS=("toml" "json" "jsonc" "bash") + +TMPDIR=$(mktemp -d) +trap "rm -rf $TMPDIR" EXIT + +for grammar in "${GRAMMARS[@]}"; do + echo "Building and installing tree-sitter-${grammar}..." + cd "$TMPDIR" + + if ! wget -q "https://github.com/tree-sitter-grammars/tree-sitter-${grammar}/archive/refs/heads/master.zip" -O "${grammar}.zip"; then + echo "ERROR: Failed to download tree-sitter-${grammar}" >&2 + exit 1 + fi + + if ! unzip -q "${grammar}.zip"; then + echo "ERROR: Failed to unzip tree-sitter-${grammar}" >&2 + exit 1 + fi + + cd "tree-sitter-${grammar}-master" + + # Compile parser.c + if ! gcc -fPIC -I./src -c src/parser.c -o parser.o; then + echo "ERROR: Failed to compile parser.c for ${grammar}" >&2 + exit 1 + fi + + # Check if scanner exists (not all grammars have scanners) + if [ -f src/scanner.c ]; then + if ! gcc -fPIC -I./src -c src/scanner.c -o scanner.o; then + echo "ERROR: Failed to compile scanner.c for ${grammar}" >&2 + exit 1 + fi + OBJECTS="parser.o scanner.o" + else + OBJECTS="parser.o" + fi + + # Link object files into shared library + if ! gcc -shared -o "libtree-sitter-${grammar}.so" $OBJECTS; then + echo "ERROR: Failed to link libtree-sitter-${grammar}.so" >&2 + exit 1 + fi + + # Install to system + if ! $SUDO cp "libtree-sitter-${grammar}.so" /usr/local/lib/; then + echo "ERROR: Failed to copy libtree-sitter-${grammar}.so to /usr/local/lib/" >&2 + exit 1 + fi + + echo " ✓ Installed tree-sitter-${grammar}" +done + +if ! $SUDO ldconfig; then + echo "WARNING: ldconfig failed, libraries may not be immediately available" >&2 +fi + +echo "" +echo "tree-sitter setup complete!" +echo "" +echo "Detected library paths:" + +# Detect and report tree-sitter runtime library location +if [ -f /usr/lib/x86_64-linux-gnu/libtree-sitter.so.0 ]; then + echo " TREE_SITTER_RUNTIME_LIB=/usr/lib/x86_64-linux-gnu/libtree-sitter.so.0" +elif [ -f /usr/lib/x86_64-linux-gnu/libtree-sitter.so ]; then + echo " TREE_SITTER_RUNTIME_LIB=/usr/lib/x86_64-linux-gnu/libtree-sitter.so" +elif [ -f /usr/lib/libtree-sitter.so.0 ]; then + echo " TREE_SITTER_RUNTIME_LIB=/usr/lib/libtree-sitter.so.0" +elif [ -f /usr/lib/libtree-sitter.so ]; then + echo " TREE_SITTER_RUNTIME_LIB=/usr/lib/libtree-sitter.so" +else + echo " WARNING: Could not find libtree-sitter runtime library!" +fi + +echo "" +echo "Grammar libraries:" +for grammar in "${GRAMMARS[@]}"; do + echo " TREE_SITTER_${grammar^^}_PATH=/usr/local/lib/libtree-sitter-${grammar}.so" +done + diff --git a/.github/workflows/ancient.yml b/.github/workflows/ancient.yml deleted file mode 100644 index a28c90c..0000000 --- a/.github/workflows/ancient.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: MRI 2.3, 2.4, 2.5 (EOL) - -permissions: - contents: read - -on: - push: - branches: - - 'main' - - '*-stable' - tags: - - '!*' # Do not execute on tags - pull_request: - branches: - - '*' - # Allow manually triggering the workflow. - workflow_dispatch: - -# Cancels all previous workflow runs for the same branch that have not yet completed. -concurrency: - # The concurrency group contains the workflow name and the branch name. - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - test: - if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" - name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} - runs-on: ubuntu-22.04 - continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps - BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile - strategy: - fail-fast: false - matrix: - include: - # Ruby 2.3 - - ruby: "ruby-2.3" - appraisal: "ruby-2-3" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: "3.3.27" - bundler: "2.3.27" - - # Ruby 2.4 - - ruby: "ruby-2.4" - appraisal: "ruby-2-4" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: "3.3.27" - bundler: "2.3.27" - - # Ruby 2.5 - - ruby: "ruby-2.5" - appraisal: "ruby-2-5" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: "3.3.27" - bundler: "2.3.27" - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Ruby & RubyGems - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - rubygems: ${{ matrix.rubygems }} - bundler: ${{ matrix.bundler }} - bundler-cache: false - - # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) - # We need to do this first to get appraisal installed. - # NOTE: This does not use the primary Gemfile at all. - - name: Install Root Appraisal - run: bundle - - name: Appraisal for ${{ matrix.appraisal }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f3cd1a7..236c9d6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,8 +6,8 @@ permissions: id-token: write env: - K_SOUP_COV_MIN_BRANCH: 86 - K_SOUP_COV_MIN_LINE: 99 + K_SOUP_COV_MIN_BRANCH: 85 + K_SOUP_COV_MIN_LINE: 96 K_SOUP_COV_MIN_HARD: true K_SOUP_COV_FORMATTERS: "xml,rcov,lcov,tty" K_SOUP_COV_DO: true @@ -63,7 +63,7 @@ jobs: ruby-version: "${{ matrix.ruby }}" rubygems: "${{ matrix.rubygems }}" bundler: "${{ matrix.bundler }}" - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. @@ -115,7 +115,7 @@ jobs: hide_complexity: true indicators: true output: both - thresholds: '99 86' + thresholds: '96 85' continue-on-error: ${{ matrix.experimental != 'false' }} - name: Add Coverage PR Comment diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index 8a60c85..6d7d1ac 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -32,8 +32,11 @@ jobs: name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }} runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + env: + # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + # Set LD_LIBRARY_PATH globally for the job to ensure the dynamic linker finds the library + LD_LIBRARY_PATH: "/usr/lib/x86_64-linux-gnu:/usr/lib" strategy: matrix: include: @@ -63,28 +66,31 @@ jobs: steps: - name: Checkout - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v6 + - name: Install tree-sitter system library + run: sudo apt-get update && sudo apt-get install -y libtree-sitter-dev + - name: Setup Ruby & RubyGems - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - name: Install Root Appraisal - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle # Continue to the next step on failure continue-on-error: true @@ -93,12 +99,12 @@ jobs: - name: "[Attempt 2] Install Root Appraisal" id: bundleAttempt2 # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true @@ -107,9 +113,9 @@ jobs: - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt2 # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/dep-heads.yml b/.github/workflows/dep-heads.yml index d912f61..1adf345 100644 --- a/.github/workflows/dep-heads.yml +++ b/.github/workflows/dep-heads.yml @@ -33,8 +33,11 @@ jobs: name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }}${{ matrix.name_extra || '' }} runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + env: + # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + # Set LD_LIBRARY_PATH globally for the job to ensure the dynamic linker finds the library + LD_LIBRARY_PATH: "/usr/lib/x86_64-linux-gnu:/usr/lib" strategy: fail-fast: true matrix: @@ -65,28 +68,31 @@ jobs: steps: - name: Checkout - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v6 + - name: Install tree-sitter system library + run: sudo apt-get update && sudo apt-get install -y libtree-sitter-dev + - name: Setup Ruby & RubyGems - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - name: Install Root Appraisal - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle # Continue to the next step on failure continue-on-error: true @@ -95,12 +101,12 @@ jobs: - name: "[Attempt 2] Install Root Appraisal" id: bundleAttempt2 # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true @@ -109,9 +115,9 @@ jobs: - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt2 # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/heads.yml b/.github/workflows/heads.yml index dcf72ff..c73e694 100644 --- a/.github/workflows/heads.yml +++ b/.github/workflows/heads.yml @@ -31,8 +31,11 @@ jobs: name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }}${{ matrix.name_extra || '' }} runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps + env: + # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile + # Set LD_LIBRARY_PATH globally for the job to ensure the dynamic linker finds the library + LD_LIBRARY_PATH: "/usr/lib/x86_64-linux-gnu:/usr/lib" strategy: fail-fast: true matrix: @@ -64,28 +67,31 @@ jobs: steps: - name: Checkout - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v6 + - name: Install tree-sitter system library + run: sudo apt-get update && sudo apt-get install -y libtree-sitter-dev + - name: Setup Ruby & RubyGems - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - name: Install Root Appraisal - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle # Continue to the next step on failure continue-on-error: true @@ -94,12 +100,12 @@ jobs: - name: "[Attempt 2] Install Root Appraisal" id: bundleAttempt2 # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true @@ -108,9 +114,9 @@ jobs: - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt2 # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/jruby.yml b/.github/workflows/jruby.yml deleted file mode 100644 index 3ff8d8c..0000000 --- a/.github/workflows/jruby.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: JRuby - -permissions: - contents: read - -env: - K_SOUP_COV_DO: false - -on: - push: - branches: - - 'main' - - '*-stable' - tags: - - '!*' # Do not execute on tags - pull_request: - branches: - - '*' - # Allow manually triggering the workflow. - workflow_dispatch: - -# Cancels all previous workflow runs for the same branch that have not yet completed. -concurrency: - # The concurrency group contains the workflow name and the branch name. - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - test: - if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" - name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} - runs-on: ubuntu-22.04 - continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps - BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile - strategy: - matrix: - include: - # jruby-9.4 (targets Ruby 3.1 compatibility) - - ruby: "jruby-9.4" - appraisal: "ruby-3-1" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: default - bundler: default - - steps: - - name: Checkout - if: ${{ !env.ACT }} - uses: actions/checkout@v6 - - - name: Setup Ruby & RubyGems - if: ${{ !env.ACT }} - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - rubygems: ${{ matrix.rubygems }} - bundler: ${{ matrix.bundler }} - bundler-cache: false - - # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) - # We need to do this first to get appraisal installed. - # NOTE: This does not use the primary Gemfile at all. - - name: Install Root Appraisal - if: ${{ !env.ACT }} - run: bundle - - name: Appraisal for ${{ matrix.appraisal }} - if: ${{ !env.ACT }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - if: ${{ !env.ACT }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml deleted file mode 100644 index 9fe3c7c..0000000 --- a/.github/workflows/legacy.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: MRI 3.0, 3.1 (EOL) - -permissions: - contents: read - -env: - K_SOUP_COV_DO: false - -on: - push: - branches: - - 'main' - - '*-stable' - tags: - - '!*' # Do not execute on tags - pull_request: - branches: - - '*' - # Allow manually triggering the workflow. - workflow_dispatch: - -# Cancels all previous workflow runs for the same branch that have not yet completed. -concurrency: - # The concurrency group contains the workflow name and the branch name. - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - test: - if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" - name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} - runs-on: ubuntu-22.04 - continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps - BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile - strategy: - fail-fast: false - matrix: - include: - # Ruby 3.0 - - ruby: "ruby-3.0" - appraisal: "ruby-3-0" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: '3.5.23' - bundler: '2.5.23' - - # Ruby 3.1 - - ruby: "ruby-3.1" - appraisal: "ruby-3-1" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: '3.6.9' - bundler: '2.6.9' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Ruby & RubyGems - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - rubygems: ${{ matrix.rubygems }} - bundler: ${{ matrix.bundler }} - bundler-cache: false - - # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) - # We need to do this first to get appraisal installed. - # NOTE: This does not use the primary Gemfile at all. - - name: Install Root Appraisal - run: bundle - - name: Appraisal for ${{ matrix.appraisal }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index c437098..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Ruby - -on: - push: - branches: - - main - - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - name: Ruby ${{ matrix.ruby }} - strategy: - matrix: - ruby: - - '3.4.7' - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - name: Run the default task - run: bundle exec rake diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index b00e227..bc4b44b 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -52,7 +52,7 @@ jobs: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. diff --git a/.github/workflows/supported.yml b/.github/workflows/supported.yml index 7937fb1..effac2d 100644 --- a/.github/workflows/supported.yml +++ b/.github/workflows/supported.yml @@ -62,7 +62,7 @@ jobs: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. diff --git a/.github/workflows/truffle.yml b/.github/workflows/truffle.yml index 53ac8ef..aaf875d 100644 --- a/.github/workflows/truffle.yml +++ b/.github/workflows/truffle.yml @@ -47,28 +47,28 @@ jobs: steps: - name: Checkout - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: actions/checkout@v6 - name: Setup Ruby & RubyGems - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. # NOTE: This does not use the primary Gemfile at all. - name: Install Root Appraisal - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle - name: "[Attempt 1] Install Root Appraisal" id: bundleAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle # Continue to the next step on failure continue-on-error: true @@ -77,12 +77,12 @@ jobs: - name: "[Attempt 2] Install Root Appraisal" id: bundleAttempt2 # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle # Continue to the next step on failure continue-on-error: true @@ -91,9 +91,9 @@ jobs: - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" id: bundleAppraisalAttempt2 # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (!env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby'))) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} + if: ${{ !env.ACT || (! startsWith(matrix.ruby, 'jruby') && !startsWith(matrix.ruby, 'truffleruby')) }} run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.github/workflows/unlocked_deps.yml b/.github/workflows/unlocked_deps.yml index 826720a..c3601d9 100644 --- a/.github/workflows/unlocked_deps.yml +++ b/.github/workflows/unlocked_deps.yml @@ -71,7 +71,7 @@ jobs: ruby-version: ${{ matrix.ruby }} rubygems: ${{ matrix.rubygems }} bundler: ${{ matrix.bundler }} - bundler-cache: false + bundler-cache: true # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) # We need to do this first to get appraisal installed. diff --git a/.github/workflows/unsupported.yml b/.github/workflows/unsupported.yml deleted file mode 100644 index 4c938cb..0000000 --- a/.github/workflows/unsupported.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: MRI 2.6 & 2.7 (EOL) - -permissions: - contents: read - -env: - K_SOUP_COV_DO: false - -on: - push: - branches: - - 'main' - - '*-stable' - tags: - - '!*' # Do not execute on tags - pull_request: - branches: - - '*' - # Allow manually triggering the workflow. - workflow_dispatch: - -# Cancels all previous workflow runs for the same branch that have not yet completed. -concurrency: - # The concurrency group contains the workflow name and the branch name. - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - test: - if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" - name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} - runs-on: ubuntu-22.04 - continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps - BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile - strategy: - fail-fast: false - matrix: - include: - # Ruby 2.6 - - ruby: "ruby-2.6" - appraisal: "ruby-2-6" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: '3.4.22' - bundler: '2.4.22' - - # Ruby 2.7 - - ruby: "ruby-2.7" - appraisal: "ruby-2-7" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: '3.4.22' - bundler: '2.4.22' - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Ruby & RubyGems - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - rubygems: ${{ matrix.rubygems }} - bundler: ${{ matrix.bundler }} - bundler-cache: false - - # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) - # We need to do this first to get appraisal installed. - # NOTE: This does not use the primary Gemfile at all. - - name: Install Root Appraisal - run: bundle - - name: Appraisal for ${{ matrix.appraisal }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock index 1bc4b40..012f826 100644 --- a/.rubocop_gradual.lock +++ b/.rubocop_gradual.lock @@ -1,5 +1,5 @@ { - "README.md:93800715": [ + "README.md:126761956": [ [318, 15, 1, "Lint/Syntax: unterminated string meets end of file\n(Using Ruby 3.2 parser; configure using `TargetRubyVersion` parameter, under `AllCops`)", 177561] ], "spec/markly/merge/branch_coverage_spec.rb:1610524572": [ diff --git a/Appraisals b/Appraisals index 2d4cd44..c59fef8 100644 --- a/Appraisals +++ b/Appraisals @@ -23,8 +23,12 @@ appraise "unlocked_deps" do eval_gemfile "modular/coverage.gemfile" eval_gemfile "modular/documentation.gemfile" eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/style.gemfile" eval_gemfile "modular/x_std_libs.gemfile" + + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end # Used for head (nightly) releases of ruby, truffleruby, and jruby. @@ -33,54 +37,46 @@ appraise "head" do # Why is gem "cgi" here? See: https://github.com/vcr/vcr/issues/1057 # gem "cgi", ">= 0.5" gem "benchmark", "~> 0.4", ">= 0.4.1" + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/x_std_libs.gemfile" + + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end # Used for current releases of ruby, truffleruby, and jruby. # Split into discrete appraisals if one of them needs a dependency locked discretely. appraise "current" do + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/x_std_libs.gemfile" + + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end # Test current Rubies against head versions of runtime dependencies appraise "dep-heads" do + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/runtime_heads.gemfile" -end - -appraise "ruby-2-3" do - eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile" -end - -appraise "ruby-2-4" do - eval_gemfile "modular/x_std_libs/r2.4/libs.gemfile" -end - -appraise "ruby-2-5" do - eval_gemfile "modular/x_std_libs/r2.6/libs.gemfile" -end -appraise "ruby-2-6" do - eval_gemfile "modular/x_std_libs/r2.6/libs.gemfile" -end - -appraise "ruby-2-7" do - eval_gemfile "modular/x_std_libs/r2/libs.gemfile" -end - -appraise "ruby-3-0" do - eval_gemfile "modular/x_std_libs/r3.1/libs.gemfile" -end - -appraise "ruby-3-1" do - eval_gemfile "modular/x_std_libs/r3.1/libs.gemfile" + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end appraise "ruby-3-2" do + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/x_std_libs/r3/libs.gemfile" + + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end appraise "ruby-3-3" do + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/x_std_libs/r3/libs.gemfile" + + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end # Only run security audit on the latest version of Ruby @@ -92,7 +88,11 @@ end appraise "coverage" do eval_gemfile "modular/coverage.gemfile" eval_gemfile "modular/optional.gemfile" + eval_gemfile "modular/rspec.gemfile" eval_gemfile "modular/x_std_libs.gemfile" + + # integration test dependencies that we can't add to gemspec due to platform differences + eval_gemfile "modular/tree_sitter.gemfile" end # Only run linter on the latest version of Ruby (but, in support of oldest supported Ruby version) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a11239..9e6780a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ Please file a bug if you notice a violation of semantic versioning. ### Changed +- **SmartMerger**: Added `**options` for forward compatibility + - Accepts additional options that may be added to base class in future + - Passes all options through to `Markdown::Merge::SmartMerger` + ### Deprecated ### Removed @@ -30,6 +34,32 @@ Please file a bug if you notice a violation of semantic versioning. ### Security -## [0.1.0] - 2025-12-08 +## [1.0.0] - 2024-12-17 + +### Added + +- Initial release of markly-merge +- Thin wrapper around `markdown-merge` for Markly backend +- `Markly::Merge::SmartMerger` - smart merging with markly defaults + - Default freeze token: `"markly-merge"` + - Default `inner_merge_code_blocks: true` (enabled by default) +- `Markly::Merge::FileAnalysis` - file analysis with markly backend +- `Markly::Merge::FreezeNode` - freeze block support +- Markly-specific parse options: + - `flags:` - Markly parse flags (e.g., `Markly::FOOTNOTES`, `Markly::SMART`) + - `extensions:` - GFM extensions (`:table`, `:strikethrough`, `:autolink`, `:tagfilter`, `:tasklist`) +- Error classes: `Error`, `ParseError`, `TemplateParseError`, `DestinationParseError` +- Re-exports shared classes from markdown-merge: + - `FileAligner`, `ConflictResolver`, `MergeResult` + - `TableMatchAlgorithm`, `TableMatchRefiner`, `CodeBlockMerger` + - `NodeTypeNormalizer` + +### Dependencies + +- `markly` (~> 0.15) - cmark-gfm C library +- `markdown-merge` (~> 1.0) - central merge infrastructure +- `version_gem` (~> 1.1) -- Initial release +[Unreleased]: https://github.com/kettle-rb/markly-merge/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/kettle-rb/markly-merge/compare/3dcd8b855b8a773f175ff34d31e3885a28a3e70b...v1.0.0 +[1.0.0t]: https://github.com/kettle-rb/markly-merge/tags/v1.0.0 diff --git a/Gemfile b/Gemfile index a11c98d..3afcef8 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,12 @@ git_source(:gitlab) { |repo_name| "https://gitlab.com/#{repo_name}" } # Specify your gem's dependencies in markly-merge.gemspec gemspec +# runtime dependencies that we can't add to gemspec due to platform differences +eval_gemfile "gemfiles/modular/tree_sitter.gemfile" + +# optional templating dependencies +eval_gemfile "gemfiles/modular/templating.gemfile" + eval_gemfile "gemfiles/modular/debug.gemfile" eval_gemfile "gemfiles/modular/coverage.gemfile" eval_gemfile "gemfiles/modular/style.gemfile" diff --git a/Gemfile.lock b/Gemfile.lock index 8bf5254..acd71d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/pboling/tree_stump.git + revision: e5d2244e2bafa7405fb943dcc5f23f47f78aacdf + branch: tree_haver + specs: + tree_stump (0.1.0) + GIT remote: https://github.com/pboling/yard-junk.git revision: 54ccebabbfa9a9cd44d0b991687ebbfd22c32b55 @@ -13,7 +20,8 @@ GIT PATH remote: ../.. specs: - ast-merge (1.0.0) + ast-merge (1.1.0) + tree_haver (~> 3.1, >= 3.1.0) version_gem (~> 1.1, >= 1.1.9) PATH @@ -21,7 +29,15 @@ PATH specs: bash-merge (1.0.0) ast-merge (~> 1.0) - ruby_tree_sitter (~> 2.0) + tree_haver (~> 3.0) + version_gem (~> 1.1, >= 1.1.9) + +PATH + remote: ../commonmarker-merge + specs: + commonmarker-merge (1.0.0) + commonmarker (~> 2.0) + markdown-merge (~> 1.0) version_gem (~> 1.1, >= 1.1.9) PATH @@ -36,7 +52,7 @@ PATH specs: json-merge (1.0.0) ast-merge (~> 1.0) - ruby_tree_sitter (~> 2.0) + tree_haver (~> 3.0) version_gem (~> 1.1, >= 1.1.9) PATH @@ -44,7 +60,7 @@ PATH specs: jsonc-merge (1.0.0) ast-merge (~> 1.0) - ruby_tree_sitter (~> 2.0) + tree_haver (~> 3.0) version_gem (~> 1.1, >= 1.1.9) PATH @@ -52,6 +68,7 @@ PATH specs: markdown-merge (1.0.0) ast-merge (~> 1.0) + tree_haver (~> 3.0) version_gem (~> 1.1, >= 1.1.9) PATH @@ -75,7 +92,7 @@ PATH specs: rbs-merge (1.0.0) ast-merge (~> 1.0) - rbs (>= 3.0) + rbs (>= 1.6) version_gem (~> 1.1, >= 1.1.9) PATH @@ -83,14 +100,19 @@ PATH specs: toml-merge (1.0.0) ast-merge (~> 1.0) - ruby_tree_sitter (~> 2.0) + tree_haver (~> 3.1, >= 3.1.0) + version_gem (~> 1.1, >= 1.1.9) + +PATH + remote: ../tree_haver + specs: + tree_haver (3.1.0) version_gem (~> 1.1, >= 1.1.9) PATH remote: . specs: markly-merge (1.0.0) - ast-merge (~> 1.0) markdown-merge (~> 1.0) markly (~> 0.15) version_gem (~> 1.1, >= 1.1.9) @@ -106,18 +128,20 @@ GEM rake (>= 10) thor (>= 0.14) ast (2.4.3) - backports (3.25.2) + backports (3.25.3) benchmark (0.5.0) bigdecimal (3.3.1) bundler-audit (0.9.3) bundler (>= 1.2.0) thor (~> 1.0) - concurrent-ruby (1.3.5) + citrus (3.0.2) + commonmarker (2.6.0-x86_64-linux) + concurrent-ruby (1.3.6) date (3.5.1) - debug (1.11.0) + debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) - delegate (0.4.0) + delegate (0.6.1) diff-lcs (1.6.2) diffy (3.4.4) docile (1.4.1) @@ -151,13 +175,14 @@ GEM dry-logic (~> 1.4) zeitwerk (~> 2.6) erb (5.1.3) + ffi (1.17.2-x86_64-linux-gnu) gem_bench (2.0.5) bundler (>= 1.14) version_gem (~> 1.1, >= 1.1.4) gitmoji-regex (1.0.3) version_gem (~> 1.1, >= 1.1.8) - io-console (0.8.1) - irb (1.15.3) + io-console (0.8.2) + irb (1.16.0) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) @@ -209,15 +234,15 @@ GEM pdf-core (~> 0.10.0) ttfunk (~> 1.8) prettyprint (0.2.0) - prism (1.6.0) - psych (5.3.0) + prism (1.7.0) + psych (5.3.1) date stringio public_suffix (7.0.0) racc (1.8.1) rainbow (3.1.1) rake (13.3.1) - rbs (3.9.5) + rbs (3.10.0) logger rdoc (6.17.0) erb @@ -341,7 +366,7 @@ GEM simplecov-rcov (0.3.7) simplecov (>= 0.4.1) simplecov_json_formatter (0.1.4) - sorbet-runtime (0.6.12845) + sorbet-runtime (0.6.12869) standard (1.52.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -362,7 +387,7 @@ GEM version_gem (>= 1.1.4, < 3) stone_checksums (1.0.3) version_gem (~> 1.1, >= 1.1.9) - stringio (3.1.9) + stringio (3.2.0) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) thor (1.4.0) @@ -371,12 +396,15 @@ GEM delegate (~> 0.1) rspec (~> 3.0) timecop (>= 0.7, < 1) + toml-rb (4.1.0) + citrus (~> 3.0, > 3.0) + racc (~> 1.7) tsort (0.2.0) ttfunk (1.8.0) bigdecimal (~> 3.1) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-emoji (4.2.0) version_gem (1.1.9) yaml-converter (0.1.0) kramdown (>= 2.4, < 3) @@ -393,7 +421,7 @@ GEM yard-yaml (0.1.0) version_gem (~> 1.1, >= 1.1.9) yaml-converter (~> 0.1) - zeitwerk (2.7.3) + zeitwerk (2.7.4) zlib (3.2.2) PLATFORMS @@ -406,9 +434,12 @@ DEPENDENCIES bash-merge! benchmark (~> 0.4, >= 0.4.1) bundler-audit (~> 0.9.2) + citrus + commonmarker-merge! debug (>= 1.1) dotenv-merge! erb (~> 5.0) + ffi gem_bench (~> 2.0, >= 2.0.5) gitmoji-regex (~> 1.0, >= 1.0.3) irb (~> 1.15, >= 1.15.2) @@ -435,10 +466,14 @@ DEPENDENCIES rubocop-rspec (~> 3.6) rubocop-ruby3_2 ruby-progressbar (~> 1.13) + ruby_tree_sitter standard (>= 1.50) stone_checksums (~> 1.0, >= 1.0.2) stringio (>= 3.0) toml-merge! + toml-rb + tree_haver! + tree_stump! yaml-converter (~> 0.1) yard (~> 0.9, >= 0.9.37) yard-fence (~> 0.8) @@ -451,16 +486,19 @@ CHECKSUMS ansi (1.5.0) sha256=5408253274e33d9d27d4a98c46d2998266fd51cba58a7eb9d08f50e57ed23592 appraisal2 (3.0.0) sha256=f1b4e742cf8ebef5e9fbb76c416a8c16edfe0727964a5a17b44adfc37b701aed ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 - ast-merge (1.0.0) - backports (3.25.2) sha256=4852613b0336f950a811b545a447ac273823578bb7bd1d3126a6ff9d5f840ff7 + ast-merge (1.1.0) + backports (3.25.3) sha256=94298d32dc3c40ca15633b54e282780b49e2db0c045f602ea1907e4f63a17235 bash-merge (1.0.0) benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218 bundler-audit (0.9.3) sha256=81c8766c71e47d0d28a0f98c7eed028539f21a6ea3cd8f685eb6f42333c9b4e9 - concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 + citrus (3.0.2) sha256=4ec2412fc389ad186735f4baee1460f7900a8e130ffe3f216b30d4f9c684f650 + commonmarker (2.6.0-x86_64-linux) sha256=cddae70929aea45c6a8aa72bfc02d15029611b0e88fee965d0151bc9453fd8bb + commonmarker-merge (1.0.0) + concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 - debug (1.11.0) sha256=1425db64cfa0130c952684e3dc974985be201dd62899bf4bbe3f8b5d6cf1aef2 - delegate (0.4.0) sha256=e4e38bc7c52883714fec69d481988be7dd81a868bc069d672305cf27e4024e02 + debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6 + delegate (0.6.1) sha256=54cf946cacfe05b1c23114edd8fbd8d54e9cea7abf36b95130ab53cc88b8f7e4 diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 diffy (3.4.4) sha256=79384ab5ca82d0e115b2771f0961e27c164c456074bd2ec46b637ebf7b6e47e3 docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e @@ -473,10 +511,11 @@ CHECKSUMS dry-schema (1.14.1) sha256=2fcd7539a7099cacae6a22f6a3a2c1846fe5afeb1c841cde432c89c6cb9b9ff1 dry-types (1.8.3) sha256=b5d97a45e0ed273131c0c3d5bc9f5633c2d1242e092ee47401ce7d5eab65c1bc erb (5.1.3) sha256=566e53057b6ba48699f824b578473b391fa8aef100aa14afad1c46725fae0e67 + ffi (1.17.2-x86_64-linux-gnu) sha256=05d2026fc9dbb7cfd21a5934559f16293815b7ce0314846fee2ac8efbdb823ea gem_bench (2.0.5) sha256=0dc0fb44a5a5eb7b2f5c1c68a5b0164d72007132822c012bac3abe976b199ead gitmoji-regex (1.0.3) sha256=538c6f49f5af6dc36d1630edb89a5a66f6e14ec5850d7fd071e0331f940e553f - io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb - irb (1.15.3) sha256=4349edff1efa7ff7bfd34cb9df74a133a588ba88c2718098b3b4468b81184aaa + io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc + irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806 json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505 json-merge (1.0.0) jsonc-merge (1.0.0) @@ -502,15 +541,15 @@ CHECKSUMS pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prawn (2.5.0) sha256=f4e20e3b4f30bf5b9ae37dad15eb421831594553aa930b2391b0fa0a99c43cb6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 - prism (1.6.0) sha256=bfc0281a81718c4872346bc858dc84abd3a60cae78336c65ad35c8fbff641c6b + prism (1.7.0) sha256=10062f734bf7985c8424c44fac382ac04a58124ea3d220ec3ba9fe4f2da65103 prism-merge (2.0.0) - psych (5.3.0) sha256=8976a41ae29ea38c88356e862629345290347e3bfe27caf654f7c5a920e95eeb + psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 psych-merge (1.0.0) public_suffix (7.0.0) sha256=f7090b5beb0e56f9f10d79eed4d5fbe551b3b425da65877e075dad47a6a1b095 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c - rbs (3.9.5) sha256=eabaaf60aee84e38cbf94839c6e1b9cd145c7295fc3cc0e88c92e4069b1119b0 + rbs (3.10.0) sha256=e75b5f1313c71c9ee0fcea68bf97d3e5fe8ec7a641d4b5cd18bbc28c94ddf298 rbs-merge (1.0.0) rdoc (6.17.0) sha256=0f50d4e568fc98195f9bb155a9e8dff6c7feabfb515fb22ef6df1d12ad5a02b7 reek (6.5.0) sha256=d26d3a492773b2bbc228888067a21afe33ac07954a17dbd64cdeae42c4c69be1 @@ -552,22 +591,25 @@ CHECKSUMS simplecov-lcov (0.9.0) sha256=7a77a31e200a595ed4b0249493056efd0c920601f53d2ef135ca34ee796346cd simplecov-rcov (0.3.7) sha256=372f50bf6df6b6350b7d0c840f2f8bdabe021861a43c26877b747c9ac96139fc simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 - sorbet-runtime (0.6.12845) sha256=70ece77f90392e51336f182e61d6cfcf7e308b35b53001029a7825a322ad6547 + sorbet-runtime (0.6.12869) sha256=7f4ffe83b46b56c46f940fdee0995041e0687be7b59df76718428f7e0c19358d standard (1.52.0) sha256=ec050e63228e31fabe40da3ef96da7edda476f7acdf3e7c2ad47b6e153f6a076 standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2 standard-rubocop-lts (1.0.10) sha256=bdce3407fb6683a305f7f2e186858033dc88013d95bdc6ec4de8df0be55a0e47 stone_checksums (1.0.3) sha256=1d7ee38b7c766c523cbf12ab886ffbae519a2c48288f9d8ecc7ca0deed0701fe - stringio (3.1.9) sha256=c111af13d3a73eab96a3bc2655ecf93788d13d28cb8e25c1dcbff89ace885121 + stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2 thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d timecop (0.9.10) sha256=12ba45ce57cdcf6b1043cb6cdffa6381fd89ce10d369c28a7f6f04dc1b0cd8eb timecop-rspec (1.0.3) sha256=005f14841bb606dcaefb060e321b5388e2e59537742bee8b3a9a9a40e598fab9 toml-merge (1.0.0) + toml-rb (4.1.0) sha256=14456ec4549e4703881bf04b83a56f3264ef99884880092d83c98a2058d95846 + tree_haver (3.1.0) + tree_stump (0.1.0) tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f ttfunk (1.8.0) sha256=a7cbc7e489cc46e979dde04d34b5b9e4f5c8f1ee5fc6b1a7be39b829919d20ca unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 - unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5 + unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f version_gem (1.1.9) sha256=0c1a0962ae543c84a00889bb018d9f14d8f8af6029d26b295d98774e3d2eb9a4 yaml-converter (0.1.0) sha256=3a8a6ce675eec8adc7ee67697ee0567e2153cad974b601c062c3a25f545ecc77 yard (0.9.38) sha256=721fb82afb10532aa49860655f6cc2eaa7130889df291b052e1e6b268283010f @@ -575,7 +617,7 @@ CHECKSUMS yard-junk (0.0.10) yard-relative_markdown_links (0.6.0) sha256=3e5ffa85cbad9bac70bee5421774b190fb25d3bdf489323a5b3534f647d1a70e yard-yaml (0.1.0) sha256=8bf7b1a69183429c07e8c521d17fbbd56598387f15e13b2c27f31c4ba821a7a7 - zeitwerk (2.7.3) sha256=b2e86b4a9b57d26ba68a15230dcc7fe6f040f06831ce64417b0621ad96ba3e85 + zeitwerk (2.7.4) sha256=2bef90f356bdafe9a6c2bd32bcd804f83a4f9b8bc27f3600fff051eb3edcec8b zlib (3.2.2) sha256=908e61263f99c1371b5422581e2d6663bd37c6b04ae13b5f8cb10b0d09379f40 BUNDLED WITH diff --git a/README.md b/README.md index 43bcb03..9e0e4f8 100644 --- a/README.md +++ b/README.md @@ -54,24 +54,31 @@ ## 🌻 Synopsis -Markly::Merge is a standalone Ruby module that intelligently merges two versions of a Markdown file using Markly (libcmark-gfm) AST analysis. It's like a smart "git merge" specifically designed for Markdown documentation files. Built on top of [ast-merge](https://github.com/kettle-rb/ast-merge), it shares the same architecture as [prism-merge](https://github.com/kettle-rb/prism-merge) for Ruby source files. +`markly-merge` is a thin wrapper around [markdown-merge](https://github.com/kettle-rb/markdown-merge) that provides: + +- **Hard dependency on Markly** - Ensures the libcmark-gfm (C) parser is installed +- **Markly-specific defaults** - Freeze token: `"markly-merge"`, `inner_merge_code_blocks: true` +- **Markly parse options** - Pass `flags:` and `extensions:` parameters For an alternative using Comrak (Rust), see [commonmarker-merge](https://github.com/kettle-rb/commonmarker-merge). -### Key Features +### Features (via markdown-merge) - **Markly-Powered**: Uses libcmark-gfm for accurate CommonMark + GFM parsing - **Structure-Aware**: Understands headings, lists, code blocks, tables, and more - **Intelligent**: Matches sections by heading structure and content signatures - **Comment-Preserving**: HTML comments are preserved in context - **Freeze Block Support**: Respects freeze markers (default: `markly-merge:freeze` / `markly-merge:unfreeze`) for merge control - customizable to match your project's conventions +- **Type Normalization**: Canonical node types work across all markdown backends - **Full Provenance**: Tracks origin of every node -- **Standalone**: Minimal dependencies - just `markly` and `ast-merge` +- **Inner-Merge Code Blocks**: Enabled by default - merges fenced code blocks using language-specific mergers - **Customizable**: - `signature_generator` - callable custom signature generators - `preference` - setting of `:template`, `:destination`, or a Hash for per-node-type preferences - `add_template_only_nodes` - setting to retain sections that do not exist in destination - `freeze_token` - customize freeze block markers (default: `"markly-merge"`) + - `flags` - Markly parse flags (e.g., `Markly::FOOTNOTES`, `Markly::SMART`) + - `extensions` - GFM extensions (`:table`, `:strikethrough`, `:autolink`, `:tagfilter`, `:tasklist`) ### Supported Node Types @@ -105,20 +112,21 @@ File.write("merged.md", result.to_markdown) This gem is part of a family of gems that provide intelligent merging for various file formats: -| Gem | Format | Parser | Description | -|-----|--------|--------|-------------| -| [ast-merge][ast-merge] | Text | internal | Shared infrastructure for all `*-merge` gems | -| [prism-merge][prism-merge] | Ruby | [Prism][prism] | Smart merge for Ruby source files | -| [psych-merge][psych-merge] | YAML | [Psych][psych] | Smart merge for YAML files | -| [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] | Smart merge for JSON files | -| [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] | ⚠️ Proof of concept; Smart merge for JSON with Comments | -| [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] | Smart merge for Bash scripts | +| Gem | Format | Parser Backend(s) | Description | +|-----|--------|-------------------|-------------| +| [tree_haver][tree_haver] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) | +| [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems | +| [prism-merge][prism-merge] | Ruby | [Prism][prism] (via [tree_haver][tree_haver]) | Smart merge for Ruby source files | +| [psych-merge][psych-merge] | YAML | [Psych][psych] (via [tree_haver][tree_haver]) | Smart merge for YAML files | +| [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] (via [tree_haver][tree_haver]) | Smart merge for JSON files | +| [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via [tree_haver][tree_haver]) | ⚠️ Proof of concept; Smart merge for JSON with Comments | +| [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via [tree_haver][tree_haver]) | Smart merge for Bash scripts | | [rbs-merge][rbs-merge] | RBS | [RBS][rbs] | Smart merge for Ruby type signatures | | [dotenv-merge][dotenv-merge] | Dotenv | internal ([dotenv][dotenv]) | Smart merge for `.env` files | -| [toml-merge][toml-merge] | TOML | [tree-sitter-toml][ts-toml] | Smart merge for TOML files | -| [markdown-merge][markdown-merge] | Markdown | _base classes_ | Shared foundation for Markdown mergers | -| [markly-merge][markly-merge] | Markdown | [Markly][markly] | Smart merge for Markdown (CommonMark via libcmark-gfm) | -| [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] | Smart merge for Markdown (CommonMark via comrak) | +| [toml-merge][toml-merge] | TOML | [tree-sitter-toml][ts-toml] (via [tree_haver][tree_haver]) | Smart merge for TOML files | +| [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via [tree_haver][tree_haver]) | **Foundation**: Shared base for Markdown mergers with inner code block merging | +| [markly-merge][markly-merge] | Markdown | [Markly][markly] (via [tree_haver][tree_haver]) | Smart merge for Markdown (CommonMark via cmark-gfm C) | +| [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via [tree_haver][tree_haver]) | Smart merge for Markdown (CommonMark via comrak Rust) | **Example implementations** for the gem templating use case: @@ -127,6 +135,7 @@ This gem is part of a family of gems that provide intelligent merging for variou | [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems | | [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support | +[tree_haver]: https://github.com/kettle-rb/tree_haver [ast-merge]: https://github.com/kettle-rb/ast-merge [prism-merge]: https://github.com/kettle-rb/prism-merge [psych-merge]: https://github.com/kettle-rb/psych-merge @@ -149,7 +158,7 @@ This gem is part of a family of gems that provide intelligent merging for variou [ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml [rbs]: https://github.com/ruby/rbs [dotenv]: https://github.com/bkeepers/dotenv -[markly]: https://github.com/kivikakk/markly +[markly]: https://github.com/ioquatix/markly [commonmarker]: https://github.com/gjtorikian/commonmarker ## 💡 Info you can shake a stick at @@ -525,7 +534,7 @@ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright