Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/__pycache__
**/.cache
**/.venv
docs
test
60 changes: 60 additions & 0 deletions .github/workflows/build-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: build-docker

on:
push:
branches:
- master
tags:
- "**"
paths-ignore:
- "**.md"
pull_request:
paths-ignore:
- "**.md"
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: true
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
push: ${{ startsWith(github.ref, 'refs/tags/v') }}
tags: |-
${{ case(startsWith(github.ref, 'refs/tags/v'),
format('{0}{1}', 'marphux/jimmy:latest,marphux/jimmy:', github.ref_name),
'marphux/jimmy:latest'
) }}
outputs: type=docker,dest=${{ runner.temp }}/myimage.tar
- name: Check image size
# and load the image
# https://docs.docker.com/build/ci/github-actions/share-image-jobs/
run: |
docker load --input ${{ runner.temp }}/myimage.tar
docker images "*/jimmy*"
- name: Setup bats and bats libs
uses: bats-core/bats-action@4.0.0
with:
# workaround for macos, see https://github.com/bats-core/bats-action/issues/57#issuecomment-4004063838
support-path: "${{ github.workspace }}/tests/bats-support"
assert-path: "${{ github.workspace }}/tests/bats-assert"
detik-path: "${{ github.workspace }}/tests/bats-detik"
file-path: "${{ github.workspace }}/tests/bats-file"
- name: Smoke test
shell: bash
working-directory: ./test # run in a different directory to make sure that the paths are working
env:
TEST_CONTEXT: docker
run: bats smoke-test.bats --timing
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ jobs:
- name: Smoke test
shell: bash
working-directory: ./test # run in a different directory to make sure that the paths are working
env:
TEST_CONTEXT: binary
run: bats smoke-test.bats --timing
# - name: Scan for viruses (just for information)
# if: runner.os == 'Linux'
Expand Down
51 changes: 51 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## first stage - build dependencies
FROM python:3.14-alpine AS builder
WORKDIR /app

# Install git, needed for some python packages
# Install binutils and upx for reducing the size of the pandoc binary
RUN apk add --no-cache git binutils upx

RUN python -m venv /opt/venv
# enable venv
ENV PATH="/opt/venv/bin:$PATH"

# Install dependencies
COPY requirements/requirements.txt /jimmy/requirements/requirements.txt
RUN pip install --no-cache-dir -r /jimmy/requirements/requirements.txt

# Download binaries
COPY download_binaries.py /jimmy
RUN pip install requests --no-cache-dir && python /jimmy/download_binaries.py
# stripping doesn't have any effect - done in pandoc build already
# RUN strip /jimmy/bin/pandoc
# Don't use upx. It can reduce the binary size, but it slows down the execution (~60 s for a smoke test).
# It seems like it is unpacked at every single call.
# RUN upx --best --lzma /jimmy/bin/pandoc

# Copy the jimmy module
COPY jimmy /jimmy/jimmy

# Install jimmy
COPY pyproject.toml readme.md /jimmy
RUN pip install --no-cache-dir -e /jimmy

# remove unnecessary files from the venv
RUN find /opt/venv -name "*.so" -exec strip --strip-unneeded {}; \
find /opt/venv -type d -name "__pycache__" -prune -exec rm -rf {} +; \
find /opt/venv -type d -name "tests" -prune -exec rm -rf {} +
RUN pip uninstall -y pip setuptools wheel

## second stage - only necessary files
FROM python:3.14-alpine AS runner
# enable venv
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY --from=builder /jimmy/jimmy /jimmy/jimmy
COPY --from=builder /jimmy/bin /jimmy/bin

# better readable output, but not too much, since the TUI seems to be fixed to this width
ENV COLUMNS=160

# Set the entrypoint
ENTRYPOINT ["python", "/jimmy/jimmy/jimmy_cli.py"]
22 changes: 22 additions & 0 deletions docs/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

```
docker build -t marph91/jimmy .

# Mount your input and output directories
docker run -v /path/to/your/notes:/data/input -v /path/to/output:/data/output jimmy jimmy cli --format joplin /data/input/your-notes.jex --output-folder /data/output

# TUI
docker run -it jimmy tui

# CLI
docker run jimmy cli

docker run \
-v ./test/data/test_data/colornote/test_1_frontmatter/:/data/input \
-v ./tmp/output/:/data/output \
--user $(id -u):$(id -g) \
jimmy cli --format colornote /data/input/colornote-20241014.backup --password 1234 --output-folder /data/output --stdout-log-level DEBUG


docker push YOUR_DOCKER_USERNAME/concepts-build-image-demo
```
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ title: Jimmy - Note Conversion Tool

## Installation

**Download Jimmy here: [Linux](https://github.com/marph91/jimmy/releases/latest/download/jimmy-linux) | [Windows](https://github.com/marph91/jimmy/releases/latest/download/jimmy-windows.exe) | [macOS](https://github.com/marph91/jimmy/releases/latest/download/jimmy-darwin-arm64)**
**Download Jimmy here: [Linux](https://github.com/marph91/jimmy/releases/latest/download/jimmy-linux) | [Windows](https://github.com/marph91/jimmy/releases/latest/download/jimmy-windows.exe) | [macOS](https://github.com/marph91/jimmy/releases/latest/download/jimmy-darwin-arm64) Docker** (experimental)

## Use Cases

Expand Down
4 changes: 0 additions & 4 deletions download_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,7 @@ def download_one2html(
if __name__ == "__main__":
targetfolder = (Path(__file__).parent / "bin").resolve()
targetfolder.mkdir(exist_ok=True)
# try:
download_pandoc(version="3.10", targetfolder=targetfolder)
# except Exception as exc:
# print("pandoc download failed: ", str(exc))
# exit(1)
# try:
# download_one2html(targetfolder=targetfolder)
# except Exception as exc:
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Free your notes by converting them to Markdown.

:floppy_disk: Download: [**Linux**](https://github.com/marph91/jimmy/releases/latest/download/jimmy-linux) | [**Windows**](https://github.com/marph91/jimmy/releases/latest/download/jimmy-windows.exe) | [**macOS**](https://github.com/marph91/jimmy/releases/latest/download/jimmy-darwin-arm64) [![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/marph91/jimmy/total)](https://hanadigital.github.io/grev/?owner=marph91&repo=jimmy)
:floppy_disk: Download: [**Linux**](https://github.com/marph91/jimmy/releases/latest/download/jimmy-linux) | [**Windows**](https://github.com/marph91/jimmy/releases/latest/download/jimmy-windows.exe) | [**macOS**](https://github.com/marph91/jimmy/releases/latest/download/jimmy-darwin-arm64) [![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/marph91/jimmy/total)](https://hanadigital.github.io/grev/?owner=marph91&repo=jimmy) **Docker** (experimental)

If there is an issue at download or execution, please take a look at the [step-by-step instructions](https://marph91.github.io/jimmy/using_jimmy/).

Expand Down
65 changes: 43 additions & 22 deletions test/smoke-test.bats
Original file line number Diff line number Diff line change
@@ -1,27 +1,50 @@
# shellcheck shell=bash
# shellcheck disable=SC2068
# jimmy executable can't be quoted

bats_require_minimum_version 1.5.0

# environment variables

JIMMY_EXE="../dist/jimmy*"
DEFAULT_OUTPUT_FOLDER="$(mktemp -d)/smoke_test_bats_tmp"
if [[ ! -n "$TEST_CONTEXT" ]]; then
echo "Please set 'TEST_CONTEXT' to 'binary' or 'docker'"
exit 1
fi;

TEST_DATA_DIR=./data/test_data
if [[ "$TEST_CONTEXT" == "docker" ]]; then
JIMMY_IMAGE=marphux/jimmy
TEST_INPUT_DIR=/data/input # inside docker
elif [[ "$TEST_CONTEXT" == "binary" ]]; then
JIMMY_COMMAND=("../dist/jimmy*")
TEST_INPUT_DIR="$TEST_DATA_DIR" # in host system
else
echo "Unsupported 'TEST_CONTEXT'"
exit 1
fi
DEFAULT_OUTPUT_FOLDER="$(mktemp -d)/smoke_test_bats_tmp"
REFERENCE_DATA_DIR=./data/reference_data

# setup and teardown

setup_file() {
if [ ! -f "$JIMMY_EXE" ]; then
echo "Jimmy executable not found at $JIMMY_EXE !"
fi

mkdir -p "$DEFAULT_OUTPUT_FOLDER"
}

setup() {
TEST_OUTPUT_FOLDER="$DEFAULT_OUTPUT_FOLDER/$BATS_TEST_NAME"
JIMMY_ARGS=(--stdout-log-level DEBUG --output-folder "$TEST_OUTPUT_FOLDER")
mkdir -p "$TEST_OUTPUT_FOLDER"

if [[ -n "$JIMMY_IMAGE" ]]; then
JIMMY_EXTRA_ARGS=(--stdout-log-level DEBUG --output-folder /data/output)

# mount some volumes if the executable is a docker container
JIMMY_COMMAND=("docker" "run" "--user" "$(id -u):$(id -g)" "-v" "$TEST_DATA_DIR:$TEST_INPUT_DIR" "-v" "$TEST_OUTPUT_FOLDER:/data/output" "$JIMMY_IMAGE")
elif [[ -n "${JIMMY_COMMAND[*]}" ]]; then
JIMMY_EXTRA_ARGS=(--stdout-log-level DEBUG --output-folder "$TEST_OUTPUT_FOLDER")
else
false # need either JIMMY_IMAGE or JIMMY_COMMAND
fi
}

teardown() {
Expand All @@ -39,63 +62,61 @@ teardown_file() {
# tests

@test "non-existing folder" {
# shellcheck disable=SC2086
# jimmy executable can't be quoted
run ! $JIMMY_EXE cli "dsadsadsafadffasdasdsafsfdsafsdfdsafdasfdsfafsd" "${JIMMY_ARGS[@]}"
run ! ${JIMMY_COMMAND[@]} cli "dsadsadsafadffasdasdsafsfdsafsdfdsafdasfdsfafsd" "${JIMMY_EXTRA_ARGS[@]}"
}

@test "default pandoc converter" {
$JIMMY_EXE cli "$TEST_DATA_DIR/default_format/arbitrary_folder" "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/default_format/arbitrary_folder" "${JIMMY_EXTRA_ARGS[@]}"
diff "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/default_format/single_folder"
}

@test "frontmatter module (parsing and writing)" {
$JIMMY_EXE cli "$TEST_DATA_DIR/obsidian/test_1_frontmatter/vault" --format obsidian --frontmatter joplin "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/obsidian/test_1_frontmatter/vault" --format obsidian --frontmatter joplin "${JIMMY_EXTRA_ARGS[@]}"
diff --strip-trailing-cr "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/obsidian/test_1_frontmatter"
}

@test "filter - exclude tags" {
$JIMMY_EXE cli "$TEST_DATA_DIR/obsidian/test_1_frontmatter" --format obsidian --exclude-tags "*" "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/obsidian/test_1_frontmatter" --format obsidian --exclude-tags "*" "${JIMMY_EXTRA_ARGS[@]}"
}

@test "filter - include notes with tags" {
$JIMMY_EXE cli "$TEST_DATA_DIR/obsidian/test_1_frontmatter" --format obsidian --include-notes-with-tags "*" "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/obsidian/test_1_frontmatter" --format obsidian --include-notes-with-tags "*" "${JIMMY_EXTRA_ARGS[@]}"
}

@test "filter - include notes" {
$JIMMY_EXE cli "$TEST_DATA_DIR/obsidian/test_1_frontmatter" --format obsidian --include-notes "Second sample note" "Sample note" "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/obsidian/test_1_frontmatter" --format obsidian --include-notes "Second sample note" "Sample note" "${JIMMY_EXTRA_ARGS[@]}"
}

@test "textbundle converter called from bear converter" {
$JIMMY_EXE cli "$TEST_DATA_DIR/bear/test_1_frontmatter/backup.bear2bk" --format bear --frontmatter joplin "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/bear/test_1_frontmatter/backup.bear2bk" --format bear --frontmatter joplin "${JIMMY_EXTRA_ARGS[@]}"
diff "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/bear/test_1_frontmatter"
}

@test "cryptography module and password" {
$JIMMY_EXE cli "$TEST_DATA_DIR/colornote/test_1_frontmatter/colornote-20241014.backup" --format colornote --password 1234 --frontmatter joplin "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/colornote/test_1_frontmatter/colornote-20241014.backup" --format colornote --password 1234 --frontmatter joplin "${JIMMY_EXTRA_ARGS[@]}"
diff "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/colornote/test_1_frontmatter"
}

@test "sqlite3 module" {
$JIMMY_EXE cli "$TEST_DATA_DIR/qownnotes/test_1/note_folder" --format qownnotes "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/qownnotes/test_1/note_folder" --format qownnotes "${JIMMY_EXTRA_ARGS[@]}"
diff "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/qownnotes/test_1"
}

@test "pyyaml module" {
$JIMMY_EXE cli "$TEST_DATA_DIR/rednotebook/test_2/RedNotebook-Backup-2024-09-15.zip" --format rednotebook "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/rednotebook/test_2/RedNotebook-Backup-2024-09-15.zip" --format rednotebook "${JIMMY_EXTRA_ARGS[@]}"
diff "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/rednotebook/test_2"
}

@test "anytype module" {
$JIMMY_EXE cli "$TEST_DATA_DIR/anytype/test_1/Anytype.20241112.175339.64" --format anytype "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/anytype/test_1/Anytype.20241112.175339.64" --format anytype "${JIMMY_EXTRA_ARGS[@]}"
}

@test "onenote conversion with bundled executable" {
skip "onenote disabled for now"
$JIMMY_EXE cli "$TEST_DATA_DIR/onenote/test_1_frontmatter/OneDrive_2025-09-28.zip" --format onenote "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/onenote/test_1_frontmatter/OneDrive_2025-09-28.zip" --format onenote "${JIMMY_EXTRA_ARGS[@]}"
}

@test "pdf_oxyde module" {
$JIMMY_EXE cli "$TEST_DATA_DIR/default_format/pdf" "${JIMMY_ARGS[@]}"
${JIMMY_COMMAND[@]} cli "$TEST_INPUT_DIR/default_format/pdf" "${JIMMY_EXTRA_ARGS[@]}"
diff "$TEST_OUTPUT_FOLDER" "$REFERENCE_DATA_DIR/default_format/pdf"
}
Loading