diff --git a/.gitignore b/.gitignore
index ddb5241..3bdd893 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ go-app/_pdfs
pdfminion
docs/website/_site
docs/_site
+go-app/build
diff --git a/README.md b/README.md
index 452d7a5..5e817d0 100644
--- a/README.md
+++ b/README.md
@@ -7,15 +7,12 @@ Helper (_minion_) for some mundane tasks with PDF documents, among others:
* add header and/or footer text
* concatenate (combine multiple PDF files into a single file)
-It shall have a (multi-platform) graphical user interface, at least for Mac-OS, Windows and maybe Linux.
> minion: a servile dependent, follower, or underling.
> "He's one of the boss's minions."
> From: [Merriam-Webster Dictionary](https://www.merriam-webster.com/dictionary/minion)
## Status
-[![feature-linter](https://github.com/gernotstarke/PDFminion/actions/workflows/feature-linter.yml/badge.svg)](https://github.com/gernotstarke/PDFminion/actions/workflows/feature-linter.yml)
-[![go_test](https://github.com/gernotstarke/PDFminion/actions/workflows/go_test.yml/badge.svg)](https://github.com/gernotstarke/PDFminion/actions/workflows/go_test.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/gernotstarke/PDFminion)](https://goreportcard.com/report/github.com/gernotstarke/PDFminion)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gernotstarke_PDFminion&metric=alert_status)](https://sonarcloud.io/dashboard?id=gernotstarke_PDFminion)
@@ -23,74 +20,8 @@ It shall have a (multi-platform) graphical user interface, at least for Mac-OS,
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
I'm currently experimenting with an MVP version of PDFminion, therefore most of the information given here is **NOT** valid any longer.
-## Why PDFminion?
+## Why PDFminion?
-## Development
-
-#### The following paragraphs are outdated!!
-
-We're using BDD (behavior driven development) with Cucumber to specify at least part of the requirements as _scenarios_.
-These scenarios can be executed, similar to automated unit tests.
-
-* [Godog](https://github.com/cucumber/godog), the official Cucumber tool
-* [Cucumber HTML Reporter](https://www.npmjs.com/package/cucumber-html-reporter)
-* [Cucumber Multi Reporter (more detailed)](https://github.com/wswebcreation/multiple-cucumber-html-reporter)
-
-Use `./create-detailed-cucumber-report.sh` to generate a detailed BDD Cucumber report.
-
-### Deviation from Standard golang practices
-As of June 2021, the `godog` bdd tool does not respect the standard golang layout practice
-of putting test files next to the tested-code.
-Instead, the step definitions need to be present in the root folder!
-
-To avoid confusion, I prefixed the step definitions with `stepdef_` - so they are easily recognizable.
-Other (non-bdd/cucumber) automated tests will reside within the appropriate package folders.
-
-See this [Cucumber/godog issue](https://github.com/cucumber/godog/issues/373).
-
-### Godog
-
-````shell
-go get github.com/cucumber/godog/cmd/godog@v0.12.0
-````
-### Cucumber HTML Reporter
-
-It's written in JavaScript and requires `npm` and `node` to be available on your machine.
-
-```shell
-npm install cucumber-html-reporter --save-dev
-```
-
-### Cucumber Multi Reporter
-
-Again, JavaScript, see above:
-
-```shell
-npm install multiple-cucumber-html-reporter --save-dev
-```
-
-
-## Usage of Development Tools
-
-I squeezed the required commands into the files `create-cucumber-report.sh`
-and `create-detailed-cucumber-report.sh`.
-
-### Godog
-
-```shell
-godog --format cucumber:test-results-results/cucumber-report.json
-```
-
-Notes:
-
-* godog requires features and scenarios to be written in a `features` directory.
-* the `--format` switch can take a file and/or directory name
-
-
-### Cucumber Report
-
-```shell
-node ./assets/simple-cucumber-report.js
-```
-
+When creating PDF documents, you often need to add page numbers, headers, footers, or combine multiple PDF files into a single file.
+PDFminion can do that for you - from a terminal and the command line, on MacOS, Linux and Windows platforms.
diff --git a/changelog.md b/changelog.md
index 50c5208..aa94123 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,5 @@
## PDFminion version history
-
+0.3.1 Nov 16th 2024: refactored repository layout, fixed wrong output
0.3.0 better structure, minimal main package, moved logic to process.go
0.2.5 fixed nasty bug in numbering
0.2.3 add --force flag to allow existing target directory
diff --git a/documentation/adr/.adr-dir b/documentation/adr/.adr-dir
new file mode 100644
index 0000000..9c558e3
--- /dev/null
+++ b/documentation/adr/.adr-dir
@@ -0,0 +1 @@
+.
diff --git a/documentation/adr/0001-use-adrs.md b/documentation/adr/0001-use-adrs.md
new file mode 100644
index 0000000..d8d4924
--- /dev/null
+++ b/documentation/adr/0001-use-adrs.md
@@ -0,0 +1,46 @@
+# 1. Use-ADRs
+# Architecture Decision Record: Using ADRs for Technical Documentation
+
+## Status
+Accepted
+
+## Date: 2024-11-18
+
+## Context
+We need a sustainable way to document technical decisions that:
+- Captures the context and reasoning at the time of decision
+- Is easy to maintain alongside code
+- Provides historical context for future maintainers
+- Supports clear communication within the team
+
+## Decision
+We will use Architecture Decision Records (ADRs) as our primary means of documenting significant technical decisions.
+
+## Format
+Each ADR will be:
+- Written in Markdown
+- Stored in `/documentation/adr` directory
+- Named using pattern: `NNNN-title-with-dashes.md`
+- Include sections: Status, Date, Context, Decision, Consequences
+
+## Reasons
+
+1. **Time-Stamped Context**
+- Captures why decisions were made at a specific point in time
+- Helps future maintainers understand historical choices
+
+2. **Version Control Integration**
+- ADRs live with the code
+- Changes tracked in git
+
+
+### Cons
+1. **Maintenance Required**
+- Must be kept up to date
+- Requires discipline to create consistently
+
+
+## Notes
+- ADRs are immutable once accepted
+- Superseded decisions should be marked as such
+- Not every decision needs an ADR - focus on significant architectural choices
\ No newline at end of file
diff --git a/documentation/adr/0002-use-make-as-build-tool.md b/documentation/adr/0002-use-make-as-build-tool.md
new file mode 100644
index 0000000..7f40338
--- /dev/null
+++ b/documentation/adr/0002-use-make-as-build-tool.md
@@ -0,0 +1,107 @@
+# 2. Use-make-as-build-tool
+
+## Status
+Accepted
+
+## Date
+2024-11-18
+
+## Context
+We needed to choose a build tool for our Go application PDFminion that would handle:
+- Cross-platform compilation
+- Multiple build targets
+- Release packaging
+- Installation/uninstallation
+- Development workflows
+
+The main alternatives considered were:
+- Make
+- Shell scripts
+- Go's built-in build commands
+
+## Decision
+We decided to use Make as our primary build tool.
+
+## Reasons
+
+### Pros
+1. **Ubiquity**
+- Make is installed by default on most Unix-like systems
+- Well-understood by most developers
+- Extensive documentation available
+- Long history of reliability
+
+2. **Platform Independence**
+- Works on all major platforms (Linux, macOS, Windows via WSL)
+- Consistent behavior across environments
+
+3. **Dependency Management**
+- Built-in dependency tracking
+- Efficient rebuilds (only rebuilds what's necessary)
+- Clear visualization of build dependencies
+
+4. **Simplicity**
+- Declarative syntax
+- No additional dependencies needed
+- Easy to maintain and modify
+- Self-documenting through target names
+
+5. **Flexibility**
+- Can execute any shell command
+- Easy to add new targets
+- Supports both simple and complex build processes
+- Can integrate with other tools seamlessly
+
+
+### Cons
+1. **Windows Compatibility**
+- Requires WSL or MinGW on Windows
+- May create friction for Windows developers
+
+2. **Syntax**
+- Tab-based syntax can be error-prone
+- Learning curve for complex features
+- Limited string manipulation capabilities
+
+3. **Error Handling**
+- Basic error handling capabilities
+- Can be verbose for complex error scenarios
+
+4. **Debugging**
+- Limited built-in debugging facilities
+- Can be hard to troubleshoot complex makefiles
+
+## Consequences
+
+### Positive
+1. **Development Workflow**
+- Simple commands like `make mac` or `make release`
+- Easy to remember and use
+- Quick to execute
+- Consistent across team members
+
+2. **Maintenance**
+- Single file (Makefile) contains all build logic
+- Easy to add new targets and modify existing ones
+- Version control friendly
+- Self-documenting
+
+3. **Integration**
+- Easy integration with CI/CD pipelines
+- Works well with existing Go tools
+- Can be extended with shell scripts if needed
+
+### Negative
+1. **Team Requirements**
+- Team members need basic Make knowledge
+- Windows developers need additional setup
+- May need documentation for complex targets
+
+2. **Scaling**
+- Complex build processes may become hard to maintain
+- Limited modularity compared to modern build tools
+- May need to supplement with scripts for complex tasks
+
+## Notes
+- Maintain clear documentation of available make targets
+
diff --git a/go-app/Makefile b/go-app/Makefile
index a02bd74..0dad534 100644
--- a/go-app/Makefile
+++ b/go-app/Makefile
@@ -5,52 +5,177 @@ GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
-GOGET=$(GOCMD) get
BINARY_NAME=pdfminion
-BINARY_UNIX=$(BINARY_NAME)_unix
+VERSION=0.3.1
+
+# Build directories
+BUILD_DIR=build
+DIST_DIR=dist
# Build information
BUILDTIME=$(shell date -u +'%Y %b %d %H:%M')
+COMMIT=$(shell git rev-parse --short HEAD)
+
+# Detect host platform
+UNAME_S := $(shell uname -s)
+UNAME_M := $(shell uname -m)
+
+# Convert to normalized platform string
+ifeq ($(UNAME_S),Darwin)
+ HOST_OS := MacOS
+else ifeq ($(UNAME_S),Linux)
+ HOST_OS := linux
+else
+ HOST_OS := unknown
+endif
-# Build flags
-LDFLAGS=-ldflags "-s -w -X 'pdfminion/internal/config.BuildTime=$(BUILDTIME)'"
+ifeq ($(UNAME_M),x86_64)
+ HOST_ARCH := amd64
+else ifeq ($(UNAME_M),arm64)
+ HOST_ARCH := arm64
+else
+ HOST_ARCH := unknown
+endif
-# Install directory
+HOST_PLATFORM := $(HOST_OS)-$(HOST_ARCH)
+
+# Add to LDFLAGS
+LDFLAGS=-ldflags "-s -w \
+ -X 'pdfminion/internal/cli.buildTime=$(BUILDTIME)' \
+ -X 'pdfminion/internal/cli.hostPlatform=$(HOST_PLATFORM)'"
+# Install directory (for Unix-like systems)
INSTALL_DIR=/usr/local/bin
-.PHONY: all build clean test run install uninstall
+# Platform specific settings
+WINDOWS_AMD64=windows-amd64
+LINUX_AMD64=linux-amd64
+DARWIN_AMD64=darwin-amd64
+DARWIN_ARM64=darwin-arm64
+
+.PHONY: all build clean test run install uninstall release \
+ build-windows-amd64 build-linux-amd64 build-darwin-amd64 build-darwin-arm64 \
+ package-windows-amd64 package-linux-amd64 package-darwin-amd64 package-darwin-arm64 \
+ mac
+
+# Directory creation
+$(BUILD_DIR):
+ mkdir -p $(BUILD_DIR)
-all: test build
+$(DIST_DIR):
+ mkdir -p $(DIST_DIR)
-build:
+# Standard development build (current platform)
+build: $(BUILD_DIR)
+ @echo "Building for current platform..."
@echo "Build Time: $(BUILDTIME)"
- @echo "LDFLAGS: $(LDFLAGS)"
- $(GOBUILD) $(LDFLAGS) -o $(BINARY_NAME) -v ./cmd/pdfminion
+ $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) -v ./cmd/pdfminion
-test:
- $(GOTEST) -v ./...
+# Shortcut for Apple Silicon build
+mac: build-darwin-arm64
+ @echo "Apple Silicon build available in $(BUILD_DIR)/$(BINARY_NAME)-$(DARWIN_ARM64)/$(BINARY_NAME)"
+
+# Platform specific builds
+build-windows-amd64: $(BUILD_DIR)
+ @echo "Building for Windows (amd64)..."
+ GOOS=windows GOARCH=amd64 $(GOBUILD) $(LDFLAGS) \
+ -o $(BUILD_DIR)/$(BINARY_NAME)-$(WINDOWS_AMD64)/$(BINARY_NAME).exe \
+ -v ./cmd/pdfminion
+
+build-linux-amd64: $(BUILD_DIR)
+ @echo "Building for Linux (amd64)..."
+ GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) \
+ -o $(BUILD_DIR)/$(BINARY_NAME)-$(LINUX_AMD64)/$(BINARY_NAME) \
+ -v ./cmd/pdfminion
+
+build-darwin-amd64: $(BUILD_DIR)
+ @echo "Building for macOS (amd64)..."
+ GOOS=darwin GOARCH=amd64 $(GOBUILD) $(LDFLAGS) \
+ -o $(BUILD_DIR)/$(BINARY_NAME)-$(DARWIN_AMD64)/$(BINARY_NAME) \
+ -v ./cmd/pdfminion
+build-darwin-arm64: $(BUILD_DIR)
+ @echo "Building for macOS (Apple Silicon)..."
+ GOOS=darwin GOARCH=arm64 $(GOBUILD) $(LDFLAGS) \
+ -o $(BUILD_DIR)/$(BINARY_NAME)-$(DARWIN_ARM64)/$(BINARY_NAME) \
+ -v ./cmd/pdfminion
+
+# Platform specific packaging
+package-windows-amd64: build-windows-amd64 $(DIST_DIR)
+ @echo "Packaging Windows (amd64) build..."
+ cd $(BUILD_DIR) && \
+ zip -r ../$(DIST_DIR)/$(BINARY_NAME)-$(WINDOWS_AMD64)-$(VERSION).zip \
+ $(BINARY_NAME)-$(WINDOWS_AMD64)
+ @echo "Windows package created in $(DIST_DIR)"
+
+package-linux-amd64: build-linux-amd64 $(DIST_DIR)
+ @echo "Packaging Linux (amd64) build..."
+ cd $(BUILD_DIR) && \
+ tar czf ../$(DIST_DIR)/$(BINARY_NAME)-$(LINUX_AMD64)-$(VERSION).tar.gz \
+ $(BINARY_NAME)-$(LINUX_AMD64)
+ @echo "Linux package created in $(DIST_DIR)"
+
+package-darwin-amd64: build-darwin-amd64 $(DIST_DIR)
+ @echo "Packaging macOS (amd64) build..."
+ cd $(BUILD_DIR) && \
+ tar czf ../$(DIST_DIR)/$(BINARY_NAME)-$(DARWIN_AMD64)-$(VERSION).tar.gz \
+ $(BINARY_NAME)-$(DARWIN_AMD64)
+ @echo "macOS package created in $(DIST_DIR)"
+
+package-darwin-arm64: build-darwin-arm64 $(DIST_DIR)
+ @echo "Packaging macOS (Apple Silicon) build..."
+ cd $(BUILD_DIR) && \
+ tar czf ../$(DIST_DIR)/$(BINARY_NAME)-$(DARWIN_ARM64)-$(VERSION).tar.gz \
+ $(BINARY_NAME)-$(DARWIN_ARM64)
+ @echo "macOS (Apple Silicon) package created in $(DIST_DIR)"
+
+# Build all platforms
+release: package-windows-amd64 package-linux-amd64 package-darwin-amd64 package-darwin-arm64
+ @echo "All platform builds completed!"
+ @ls -l $(DIST_DIR)
+
+# Build darwin universal binary
+build-darwin-universal: $(BUILD_DIR)
+ @echo "Building Universal macOS binary..."
+ GOOS=darwin GOARCH=amd64 $(GOBUILD) $(LDFLAGS) \
+ -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64/$(BINARY_NAME) \
+ -v ./cmd/pdfminion
+ GOOS=darwin GOARCH=arm64 $(GOBUILD) $(LDFLAGS) \
+ -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64/$(BINARY_NAME) \
+ -v ./cmd/pdfminion
+ lipo -create \
+ $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64/$(BINARY_NAME) \
+ $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64/$(BINARY_NAME) \
+ -output $(BUILD_DIR)/$(BINARY_NAME)-darwin-universal/$(BINARY_NAME)
+
+package-darwin-universal: build-darwin-universal $(DIST_DIR)
+ @echo "Packaging Universal macOS build..."
+ cd $(BUILD_DIR) && \
+ tar czf ../$(DIST_DIR)/$(BINARY_NAME)-darwin-universal-$(VERSION).tar.gz \
+ $(BINARY_NAME)-darwin-universal
+ @echo "macOS Universal package created in $(DIST_DIR)"
+
+# Clean build artifacts
clean:
$(GOCLEAN)
- rm -f $(BINARY_NAME)
- rm -f $(BINARY_UNIX)
+ rm -rf $(BUILD_DIR)
+ rm -rf $(DIST_DIR)
-run:
- $(GOBUILD) $(LDFLAGS) -o $(BINARY_NAME) -v ./cmd/pdfminion
- ./$(BINARY_NAME)
+# Run tests
+test:
+ $(GOTEST) -v ./...
-# Cross compilation
-build-linux:
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BINARY_UNIX) -v ./cmd/pdfminion
+# Run development build
+run: build
+ ./$(BUILD_DIR)/$(BINARY_NAME)
-# Install the binary
+# Install (Unix-like systems only)
install: build
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)"
- @sudo mv $(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
+ @sudo mv $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
@echo "Installation complete. You can now run '$(BINARY_NAME)' from anywhere."
-# Uninstall the binary
+# Uninstall
uninstall:
@echo "Uninstalling $(BINARY_NAME) from $(INSTALL_DIR)"
@sudo rm -f $(INSTALL_DIR)/$(BINARY_NAME)
- @echo "Uninstallation complete. $(BINARY_NAME) has been removed."
\ No newline at end of file
+ @echo "Uninstallation complete."
\ No newline at end of file
diff --git a/go-app/go.mod b/go-app/go.mod
index ea66308..e35a96b 100644
--- a/go-app/go.mod
+++ b/go-app/go.mod
@@ -13,10 +13,14 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 // indirect
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
+ github.com/rs/zerolog v1.33.0 // indirect
golang.org/x/image v0.5.0 // indirect
+ golang.org/x/sys v0.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go-app/go.sum b/go-app/go.sum
index e0d3ec8..1b04925 100644
--- a/go-app/go.sum
+++ b/go-app/go.sum
@@ -1,11 +1,18 @@
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ=
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEdDpxIrtFHTgnvYzA8sCQz8luv94=
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pdfcpu/pdfcpu v0.4.0 h1:381iGNvMeLP+GFqIAqgd0LSj36AsK3JH4UTaF6D5jRc=
@@ -17,6 +24,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
+github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -41,6 +51,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/go-app/internal/cli/cli.go b/go-app/internal/cli/cli.go
new file mode 100644
index 0000000..1743748
--- /dev/null
+++ b/go-app/internal/cli/cli.go
@@ -0,0 +1,176 @@
+package cli
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+ "time"
+)
+
+type Options struct {
+ SourceDir string
+ TargetDir string
+ Force bool
+}
+
+const (
+ defaultSourceDir = "_pdfs"
+ defaultTargetDir = "_target"
+ PageNrPrefix = ""
+ ChapterPrefix = "Kap."
+ ChapterPageSeparator = " - "
+)
+
+// Version information - injected at build time
+var (
+ buildTime string
+ hostPlatform string
+ appVersion string
+)
+
+func SetAppVersion(version string) {
+ appVersion = version
+}
+
+func ParseOptions() (*Options, error) {
+ opts := &Options{
+ SourceDir: defaultSourceDir,
+ TargetDir: defaultTargetDir,
+ }
+
+ // Define flags once
+ flag.StringVar(&opts.SourceDir, "source", defaultSourceDir, "Specify the source directory")
+ flag.StringVar(&opts.TargetDir, "target", defaultTargetDir, "Specify the target directory")
+ flag.BoolVar(&opts.Force, "force", false, "Forces overwrite of existing files")
+
+ version := flag.Bool("version", false, "Show version information")
+ help := flag.Bool("help", false, "Show help information")
+
+ // Add short aliases
+ flag.StringVar(&opts.SourceDir, "s", defaultSourceDir, "")
+ flag.StringVar(&opts.TargetDir, "t", defaultTargetDir, "")
+ flag.BoolVar(help, "h", false, "")
+ flag.BoolVar(version, "v", false, "")
+
+ flag.Usage = printHelp
+ flag.Parse()
+
+ // Handle help/version first
+ switch {
+ case *help:
+ printHelp()
+ os.Exit(0)
+ case *version:
+ printVersion()
+ os.Exit(0)
+ }
+
+ // Validate the options
+ if err := opts.validate(); err != nil {
+ return nil, err
+ }
+
+ return opts, nil
+}
+
+func printHelp() {
+ fmt.Printf(`PDFMinion adds page numbers to existing PDF files.
+It will take all PDF files from the source directory and put the numbered copies into the target directory.
+Furthermore, it will ensure that every chapter (aka file) starts with an odd number
+by adding a single blank page to files with an un-even page count.
+When printed double-sided, every chapter will start on a right side with an odd pagenumber.
+
+Usage:
+ -s, --source string
+ Specify the source directory (default "%s")
+ -t, --target string
+ Specify the target directory (default "%s")
+ --force
+ Forces overwrite of existing files
+ -h, --help
+ Show this help message
+ -v, --version
+ Show version information
+`, defaultSourceDir, defaultTargetDir)
+}
+
+func printVersion() {
+ fmt.Printf("PDFminion version %s\n", appVersion)
+ fmt.Printf("Built on: %s\n", hostPlatform)
+ if buildTime != "" {
+ t, err := time.Parse("2006 Jan 02 15:04", buildTime)
+ if err == nil {
+ formattedTime := formatBuildTime(t)
+ fmt.Printf("Build time: %s\n", formattedTime)
+ } else {
+ fmt.Printf("Build time: %s\n", buildTime)
+ }
+ } else {
+ fmt.Println("Build time: Not available")
+ }
+}
+
+func formatBuildTime(t time.Time) string {
+ formatted := t.Format("2006 Jan 02 15:04")
+ parts := strings.Split(formatted, " ")
+ if len(parts) == 4 {
+ day := parts[2]
+ parts[2] = day + getDaySuffix(day)
+ parts[3] += "h"
+ return strings.Join(parts, " ")
+ }
+ return formatted
+}
+
+func getDaySuffix(day string) string {
+ switch day {
+ case "01", "21", "31":
+ return "st"
+ case "02", "22":
+ return "nd"
+ case "03", "23":
+ return "rd"
+ default:
+ return "th"
+ }
+}
+
+func (o *Options) validate() error {
+ if err := o.validateSourceDir(); err != nil {
+ return err
+ }
+ return o.validateTargetDir()
+}
+func (o *Options) validateSourceDir() error {
+ if _, err := os.Stat(o.SourceDir); os.IsNotExist(err) {
+ return fmt.Errorf("Source directory '%s' does not exist", o.SourceDir)
+ }
+ return nil
+}
+
+func (o *Options) validateTargetDir() error {
+ if _, err := os.Stat(o.TargetDir); os.IsNotExist(err) {
+ fmt.Printf("Target directory '%s' does not exist. Creating it...\n", o.TargetDir)
+ if err := os.MkdirAll(o.TargetDir, os.ModePerm); err != nil {
+ return fmt.Errorf("Failed to create directory '%s': %v", o.TargetDir, err)
+ }
+ return nil
+ }
+
+ if o.Force {
+ return nil
+ }
+
+ files, err := ioutil.ReadDir(o.TargetDir)
+ if err != nil {
+ return fmt.Errorf("Cannot read directory '%s': %v", o.TargetDir, err)
+ }
+
+ if len(files) > 0 {
+ return fmt.Errorf("Target directory '%s' is not empty. Use --force to override", o.TargetDir)
+ }
+
+ return nil
+}
diff --git a/go-app/internal/config/config.go b/go-app/internal/config/config.go
deleted file mode 100644
index 43d1851..0000000
--- a/go-app/internal/config/config.go
+++ /dev/null
@@ -1,181 +0,0 @@
-package config
-
-import (
- "flag"
- "fmt"
- "io/ioutil"
- "os"
- "strings"
- "time"
-)
-
-type Config struct {
- SourceDir string
- TargetDir string
- showHelp bool
- showVersion bool
- Force bool
-}
-
-const (
- defaultSourceDir = "_pdfs"
- defaultTargetDir = "_target"
- Version = "0.3.0"
-)
-
-const PageNrPrefix = ""
-const ChapterPrefix = "Kap."
-const ChapterPageSeparator = " - "
-
-// BuildTime will be injected at build time
-var BuildTime string
-
-func New() *Config {
- return &Config{
- SourceDir: defaultSourceDir,
- TargetDir: defaultTargetDir,
- Force: false,
- }
-}
-
-func (c *Config) ParseFlags() {
- flag.StringVar(&c.SourceDir, "s", defaultSourceDir, "Specify the source directory")
- flag.StringVar(&c.SourceDir, "source", defaultSourceDir, "Specify the source directory")
- flag.StringVar(&c.TargetDir, "t", defaultTargetDir, "Specify the target directory")
- flag.StringVar(&c.TargetDir, "target", defaultTargetDir, "Specify the target directory")
- flag.BoolVar(&c.Force, "force", false, "Skips check of empty target directory, forces overwrite of existing files")
- flag.BoolVar(&c.showHelp, "h", false, "Show help information")
- flag.BoolVar(&c.showHelp, "help", false, "Show help information")
- flag.BoolVar(&c.showVersion, "v", false, "Show version information")
- flag.BoolVar(&c.showVersion, "version", false, "Show version information")
-
- // Custom usage function
- flag.Usage = c.printHelp
-
- // Parse flags
- flag.Parse()
-
- // Check for unrecognized arguments or "help"
- if flag.NArg() > 0 {
- arg := flag.Arg(0)
- if arg == "help" || arg == "?" || strings.HasPrefix(arg, "-") {
- c.showHelp = true
- }
- }
-
- // Check for "--" prefixed long-form flags
- for i, arg := range os.Args {
- switch arg {
- case "--source":
- if i+1 < len(os.Args) {
- c.SourceDir = os.Args[i+1]
- }
- case "--target":
- if i+1 < len(os.Args) {
- c.TargetDir = os.Args[i+1]
- }
- case "--help":
- c.showHelp = true
- case "--version":
- c.showVersion = true
- }
- }
-}
-
-func (c *Config) Evaluate() error {
- if c.showHelp {
- c.printHelp()
- os.Exit(0)
- }
-
- if c.showVersion {
- c.printVersion()
- os.Exit(0)
- }
-
- return c.validate()
-}
-
-func (c *Config) printHelp() {
- fmt.Println("PDFMinion adds page numbers to existing PDF files.")
- fmt.Println("It will take all PDF files from the source directory and put the numbered copies into the target directory.")
- fmt.Println("Furthermore, it will ensure that every chapter (aka file) starts with an odd number")
- fmt.Println("by adding a single blank page to files with an un-even page count.")
- fmt.Println("When printed double-sided, every chapter will start on a right side with an odd pagenumber.")
- fmt.Println("\n\nUsage:")
- fmt.Printf(" -s, --source string\n\tSpecify the source directory (default \"%s\")\n", defaultSourceDir)
- fmt.Printf(" -t, --target string\n\tSpecify the target directory (default \"%s\")\n", defaultTargetDir)
- fmt.Printf(" --force string\n\tSkips check of empty target directory, forces overwrite of existing files (default false)\n")
- fmt.Println(" -h, --help, ?, -?, help\n\tShow this help message")
- fmt.Println(" -v, --version\n\tShow version information")
-}
-
-func (c *Config) printVersion() {
- fmt.Printf("PDFminion version %s\n", Version)
- if BuildTime != "" {
- t, err := time.Parse("2006 Jan 02 15:04", BuildTime)
- if err == nil {
- formattedBuildTime := t.Format("2006 Jan 02 15:04")
- parts := strings.Split(formattedBuildTime, " ")
- if len(parts) == 4 {
- day := parts[2]
- suffix := getSuffix(day)
- parts[2] = day + suffix
- parts[3] += "h"
- formattedBuildTime = strings.Join(parts, " ")
- }
- fmt.Printf("Build time: %s\n", formattedBuildTime)
- } else {
- fmt.Printf("Build time: %s\n", BuildTime)
- }
- } else {
- fmt.Println("Build time: Not available")
- }
-}
-
-func (c *Config) validate() error {
- if err := c.validateSourceDir(); err != nil {
- return err
- }
- return c.validateTargetDir()
-}
-
-func (c *Config) validateSourceDir() error {
- if _, err := os.Stat(c.SourceDir); os.IsNotExist(err) {
- return fmt.Errorf("source directory %s does not exist", c.SourceDir)
- }
- return nil
-}
-
-func (c *Config) validateTargetDir() error {
- if _, err := os.Stat(c.TargetDir); os.IsNotExist(err) {
- fmt.Printf("Target directory %s does not exist. Creating it...\n", c.TargetDir)
- if err := os.MkdirAll(c.TargetDir, os.ModePerm); err != nil {
- return fmt.Errorf("error creating directory %s: %v", c.TargetDir, err)
- }
- } else if !c.Force {
- // Check if the directory is empty
- files, err := ioutil.ReadDir(c.TargetDir)
- if err != nil {
- return fmt.Errorf("error reading directory %s: %v", c.TargetDir, err)
- }
-
- if len(files) > 0 {
- return fmt.Errorf("target directory %s is not empty", c.TargetDir)
- }
- }
- return nil
-}
-
-func getSuffix(day string) string {
- switch day {
- case "01", "21", "31":
- return "st"
- case "02", "22":
- return "nd"
- case "03", "23":
- return "rd"
- default:
- return "th"
- }
-}
diff --git a/go-app/internal/pdf/process.go b/go-app/internal/pdf/process.go
index f04d38c..6e8123b 100644
--- a/go-app/internal/pdf/process.go
+++ b/go-app/internal/pdf/process.go
@@ -3,11 +3,11 @@ package pdf
import (
"fmt"
"log"
- "pdfminion/go-app/internal/config"
+ "pdfminion/internal/cli"
"sort"
)
-func ProcessPDFs(cfg *config.Config) error {
+func ProcessPDFs(cfg *cli.Options) error {
InitializePDFInternals()
files, err := CollectCandidatePDFs(cfg)
diff --git a/go-app/internal/pdf/processFiles.go b/go-app/internal/pdf/processFiles.go
index d6d3a28..388cfba 100644
--- a/go-app/internal/pdf/processFiles.go
+++ b/go-app/internal/pdf/processFiles.go
@@ -9,8 +9,8 @@ import (
"log"
"os"
"path/filepath"
- "pdfminion/go-app/internal/config"
- "pdfminion/go-app/internal/util"
+ "pdfminion/internal/cli"
+ "pdfminion/internal/util"
"strconv"
)
@@ -30,7 +30,7 @@ func InitializePDFInternals() {
relaxedConf.ValidationMode = model.ValidationRelaxed
}
-func CollectCandidatePDFs(cfg *config.Config) ([]string, error) {
+func CollectCandidatePDFs(cfg *cli.Options) ([]string, error) {
// count PDFs in source directory
// abort if no PDF file is present
@@ -167,10 +167,10 @@ func watermarkConfigurationForFile(chapterNr, previousPageNr, pageCount int) map
for page := 1; page <= (pageCount); page++ {
var currentPageNr = previousPageNr + page
- var chapterStr = config.ChapterPrefix + strconv.Itoa(chapterNr)
- var pageStr = config.PageNrPrefix + strconv.Itoa(currentPageNr)
+ var chapterStr = cli.ChapterPrefix + strconv.Itoa(chapterNr)
+ var pageStr = cli.PageNrPrefix + strconv.Itoa(currentPageNr)
- wmcs[page], _ = api.TextWatermark(chapterStr+config.ChapterPageSeparator+pageStr,
+ wmcs[page], _ = api.TextWatermark(chapterStr+cli.ChapterPageSeparator+pageStr,
waterMarkDescription(currentPageNr), true, false, types.POINTS)
}
return wmcs
diff --git a/go-app/internal/pdf/processSingleFile.go b/go-app/internal/pdf/processSingleFile.go
index c142c2f..5671a0d 100644
--- a/go-app/internal/pdf/processSingleFile.go
+++ b/go-app/internal/pdf/processSingleFile.go
@@ -5,7 +5,7 @@ import (
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
"log"
- "pdfminion/go-app/internal/util"
+ "pdfminion/internal/util"
"strconv"
)