Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Command line tool to extract code #389

Open
Robinlovelace opened this issue Mar 6, 2022 · 11 comments
Open

Command line tool to extract code #389

Robinlovelace opened this issue Mar 6, 2022 · 11 comments
Labels
enhancement New feature or request
Milestone

Comments

@Robinlovelace
Copy link

The knitr function purl() is really handy for extracting code chunks from RMarkdown files.

It would be really useful to have such functionality in Quarto. I've used quarto convert to great effect. Could there be command line functionality like

quarto convert test.qmd test.py

Or perhaps a separate tool, e.g.

quarto convert-to-code test.qmd test.py
@Robinlovelace
Copy link
Author

Note, currently you could do:

quarto convert test.qmd
ipython nbconvert --to python test.ipynb

A reasonable work-around for now, although I wonder how it deals with qmd files containing more than one language.

@jjallaire
Copy link
Collaborator

Implementing a purl equivalent is on our short list.

Using convert -> nbconvert would work, but as you suspected won't handle multiple languages correctly.

Note that Jupytext (https://jupytext.readthedocs.io/en/latest/) also supports Quarto (it calls quarto convert under the hood) and could do what you are looking for in a single command.

@daaronr
Copy link

daaronr commented Sep 15, 2022

Is there anything working for files that only use R code?

Ideally:

  • Keeping all actual R code (i.e., the chunks)
  • With some R ‘commented header’ noting each chunk
  • Preserving the Quarto ‘section headers’ … again as R ‘commented headers’
  • (Maybe — with tags for certain ‘code related text/comments’ to preserve)?
  • (Ideally — having it do this automatically … when you build an html … it also exports, and links this ‘R code version’)

I think this could make it a lot easier/better for Quarto people to work well with non-Quarto-ists. (It also could be useful for Quarto-lovers like myself in some situations).

@CLRafaelR
Copy link

By the way, the following command in the post by @Robinlovelace :

ipython nbconvert --to python test.ipynb

does not work with ipython: error: unrecognized arguments: ... and this command should be:

jupyter nbconvert --to python test.ipynb

@cderv
Copy link
Collaborator

cderv commented Jul 18, 2023

Duplicate of #588

@cderv cderv marked this as a duplicate of #588 Jul 18, 2023
@aemonge
Copy link

aemonge commented Feb 11, 2025

Just to make it easier, I've made a simple script for this:

#!/bin/bash

# Script: convert_qmd_to_py.sh
#
# Description: Convert .qmd files to Python scripts and optionally execute them.
#
# Usage: convert_qmd_to_py.sh [-x|--execute] [-o|--output OUTPUT_DIR] FILES...
#
# Options:
#   -x, --execute    Execute the converted Python script after conversion.
#   -o, --output     Specify the output directory for the converted Python files.
#   -h, --help       Display this help message and exit.
#
# Arguments:
#   FILES            One or more .qmd files to convert.

# Function to display help
show_help() {
    cat <<EOF
Usage: convert_qmd_to_py.sh [-x|--execute] [-o|--output OUTPUT_DIR] FILES...

Convert .qmd files to Python scripts and optionally execute them.

Options:
  -x, --execute    Execute the converted Python script after conversion.
  -o, --output     Specify the output directory for the converted Python files.
  -h, --help       Display this help message and exit.

Arguments:
  FILES            One or more .qmd files to convert.
EOF
}

# Initialize variables
execute_script=false
quiet=false
output_dir="."

# Parse command-line arguments
while [[ "$#" -gt 0 ]]; do
    case "$1" in
    -x | --execute)
        execute_script=true
        shift
        ;;
    -o | --output)
        if [[ -n "$2" && ! "$2" =~ ^- ]]; then
            output_dir="$2"
            shift 2
        else
            echo "Error: No output directory specified for -o option."
            exit 1
        fi
        ;;
    -q | --quiet)
        quiet=true
        shift
        ;;
    -h | --help)
        show_help
        exit 0
        ;;
    *)
        files+=("$1")
        shift
        ;;
    esac
done

# Check if files are provided
if [[ "${#files[@]}" -eq 0 ]]; then
    echo "Error: No .qmd files provided."
    show_help
    exit 1
fi

# Ensure output directory exists
mkdir -p "$output_dir"

# Process each .qmd file
for file in "${files[@]}"; do
    if [[ ! -f "$file" ]]; then
        echo "Error: File '$file' does not exist."
        continue
    fi

    # Extract base name without extension
    base_name=$(basename "$file" .qmd)

    # Convert .qmd to .ipynb
    if [[ "$quiet" == false ]]; then
        quarto convert "$file" --output "$base_name.ipynb"
    else
        quarto convert "$file" --output "$base_name.ipynb" 1>/dev/null 2>&1
    fi

    # Convert .ipynb to .py
    if [[ "$quiet" == false ]]; then
        jupyter nbconvert --to python "$base_name.ipynb" --output "$base_name.py"
    else
        jupyter nbconvert --to python "$base_name.ipynb" --output "$base_name.py" 1>/dev/null 2>&1
    fi

    # Remove intermediate .ipynb file
    rm "$base_name.ipynb"

    # Optionally execute the Python script
    if [[ "$execute_script" == true ]]; then
        python "$base_name.py"
        rm "$base_name.py"
    fi
done

@Robinlovelace
Copy link
Author

Awesome! Would be even better if it were functionality built into the Quarto CLI.

@Robinlovelace
Copy link
Author

Robinlovelace commented Feb 11, 2025

Thanks for the links, loads of rich content in there, e.g. the first code chunk in the last link is as follows:

quarto inspect index.qmd | jq '
  .fileInformation[].codeCells |
  map(
    .metadata = (.metadata | to_entries | map("#| \(.key): \(.value)") | join("\n"))
  ) |
  group_by(.language) |
  map({
    language: .[0].language,
    contents: map(.metadata + "\n" + .source) | join("\n")
  })
'

I wonder if the same job could be achieved with a shorter incantation at some point e.g.

quarto extract --language=Python index.qmd

@cderv
Copy link
Collaborator

cderv commented Feb 11, 2025

@Robinlovelace FWIW We plan to provide such feature in packages for now, like the R package

and possibly its python counterpart.

@Robinlovelace
Copy link
Author

Worth a lot! I like the modular approach, putting it in packages.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants