Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
M .gitignore
1
/bin/
2
/build/
3
/.gradle/
4
/gradle/
5
/.nb-gradle
6
/private
7
.nb-gradle-properties
8
scrivenvar.pro
9
out
1
dist
2
scrivenvar.bin
3
scrivenvar.exe
4
build
5
.gradle
106
A build-template
1
#!/usr/bin/env bash
2
3
# -----------------------------------------------------------------------------
4
# Copyright 2020 Dave Jarvis
5
#
6
# Permission is hereby granted, free of charge, to any person obtaining a
7
# copy of this software and associated documentation files (the
8
# "Software"), to deal in the Software without restriction, including
9
# without limitation the rights to use, copy, modify, merge, publish,
10
# distribute, sublicense, and/or sell copies of the Software, and to
11
# permit persons to whom the Software is furnished to do so, subject to
12
# the following conditions:
13
#
14
# The above copyright notice and this permission notice shall be included
15
# in all copies or substantial portions of the Software.
16
#
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
# -----------------------------------------------------------------------------
25
26
set -o errexit
27
set -o nounset
28
29
readonly SCRIPT_SRC="$(dirname "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}")"
30
readonly SCRIPT_DIR="$(cd "${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)"
31
readonly SCRIPT_NAME=$(basename "$0")
32
33
# -----------------------------------------------------------------------------
34
# The main entry point is responsible for parsing command-line arguments,
35
# changing to the appropriate directory, and running all commands requested
36
# by the user.
37
#
38
# $@ - Command-line arguments
39
# -----------------------------------------------------------------------------
40
main() {
41
  arguments "$@"
42
43
  $usage       && terminate 3
44
  requirements && terminate 4
45
  traps        && terminate 5
46
47
  directory    && terminate 6
48
  preprocess   && terminate 7
49
  execute      && terminate 8
50
  postprocess  && terminate 9
51
52
  terminate 0
53
}
54
55
# -----------------------------------------------------------------------------
56
# Perform all commands that the script requires.
57
#
58
# @return 0 - Indicate to terminate the script with non-zero exit level
59
# @return 1 - All tasks completed successfully (default)
60
# -----------------------------------------------------------------------------
61
execute() {
62
  return 1
63
}
64
65
# -----------------------------------------------------------------------------
66
# Changes to the script's working directory, provided it exists.
67
#
68
# @return 0 - Change directory failed
69
# @return 1 - Change directory succeeded
70
# -----------------------------------------------------------------------------
71
directory() {
72
  $log "Change directory"
73
  local result=1
74
75
  # Track whether change directory failed.
76
  cd "${SCRIPT_DIR}" > /dev/null 2>&1 || result=0
77
78
  return "${result}"
79
}
80
81
# -----------------------------------------------------------------------------
82
# Perform any initialization required prior to executing tasks.
83
#
84
# @return 0 - Preprocessing failed
85
# @return 1 - Preprocessing succeeded
86
# -----------------------------------------------------------------------------
87
preprocess() {
88
  $log "Preprocess"
89
90
  return 1
91
}
92
93
# -----------------------------------------------------------------------------
94
# Perform any clean up required prior to executing tasks.
95
#
96
# @return 0 - Postprocessing failed
97
# @return 1 - Postprocessing succeeded
98
# -----------------------------------------------------------------------------
99
postprocess() {
100
  $log "Postprocess"
101
102
  return 1
103
}
104
105
# -----------------------------------------------------------------------------
106
# Check that all required commands are available.
107
#
108
# @return 0 - At least one command is missing
109
# @return 1 - All commands are available
110
# -----------------------------------------------------------------------------
111
requirements() {
112
  $log "Verify requirements"
113
  local -r expected_count=${#DEPENDENCIES[@]}
114
  local total_count=0
115
116
  # Verify that each command exists.
117
  for dependency in "${DEPENDENCIES[@]}"; do
118
    # Extract the command name [0] and URL [1].
119
    IFS=',' read -ra dependent <<< "${dependency}"
120
121
    required "${dependent[0]}" "${dependent[1]}"
122
    total_count=$(( total_count + $? ))
123
  done
124
125
  unset IFS
126
127
  # Total dependencies found must match the expected number.
128
  # Integer-only division rounds down.
129
  return $(( total_count / expected_count ))
130
}
131
132
# -----------------------------------------------------------------------------
133
# Called before terminating the script.
134
# -----------------------------------------------------------------------------
135
cleanup() {
136
  $log "Cleanup"
137
}
138
139
# -----------------------------------------------------------------------------
140
# Terminates the program immediately.
141
# -----------------------------------------------------------------------------
142
trap_control_c() {
143
  $log "Interrupted"
144
  cleanup
145
  error "⯃"
146
  terminate 1
147
}
148
149
# -----------------------------------------------------------------------------
150
# Configure signal traps.
151
#
152
# @return 1 - Signal traps are set.
153
# -----------------------------------------------------------------------------
154
traps() {
155
  # Suppress echoing ^C if pressed.
156
  stty -echoctl
157
  trap trap_control_c INT
158
159
  return 1
160
}
161
162
# -----------------------------------------------------------------------------
163
# Check for a required command.
164
#
165
# $1 - Command or file to check for existence
166
# $2 - Command's website (e.g., download for binaries and source code)
167
#
168
# @return 0 - Command is missing
169
# @return 1 - Command exists
170
# -----------------------------------------------------------------------------
171
required() {
172
  local result=0
173
174
  test -f "$1" || \
175
  command -v "$1" > /dev/null 2>&1 && result=1 || \
176
    warning "Missing: $1 ($2)"
177
178
  return ${result}
179
}
180
181
# -----------------------------------------------------------------------------
182
# Show acceptable command-line arguments.
183
#
184
# @return 0 - Indicate script may not continue
185
# -----------------------------------------------------------------------------
186
utile_usage() {
187
  printf "Usage: %s [OPTIONS...]\n\n" "${SCRIPT_NAME}" >&2
188
189
  # Number of spaces to pad after the longest long argument.
190
  local -r PADDING=2
191
192
  # Determine the longest long argument to adjust spacing.
193
  local -r LEN=$(printf '%s\n' "${ARGUMENTS[@]}" | \
194
    awk -F"," '{print length($2)+'${PADDING}'}' | sort -n | tail -1)
195
196
  local duplicates
197
198
  for argument in "${ARGUMENTS[@]}"; do
199
    # Extract the short [0] and long [1] arguments and description [2].
200
    arg=("$(echo ${argument} | cut -d ',' -f1)" \
201
         "$(echo ${argument} | cut -d ',' -f2)" \
202
         "$(echo ${argument} | cut -d ',' -f3-)")
203
204
    duplicates+=("${arg[0]}")
205
206
    printf "  -%s, --%-${LEN}s%s\n" "${arg[0]}" "${arg[1]}" "${arg[2]}" >&2
207
  done
208
209
  # Sort the arguments to make sure no duplicates exist.
210
  duplicates=$(echo "${duplicates[@]}" | tr ' ' '\n' | sort | uniq -c -d)
211
212
  # Warn the developer that there's a duplicate command-line option.
213
  if [ -n "${duplicates}" ]; then
214
    # Trim all the whitespaces
215
    duplicates=$(echo "${duplicates}" | xargs echo -n)
216
    error "Duplicate command-line argument exists: ${duplicates}"
217
  fi
218
219
  return 0
220
}
221
222
# -----------------------------------------------------------------------------
223
# Write coloured text to standard output.
224
#
225
# $1 - Text to write
226
# $2 - Text's colour
227
# -----------------------------------------------------------------------------
228
coloured_text() {
229
  printf "%b%s%b\n" "$2" "$1" "${COLOUR_OFF}"
230
}
231
232
# -----------------------------------------------------------------------------
233
# Write a warning message to standard output.
234
#
235
# $1 - Text to write
236
# -----------------------------------------------------------------------------
237
warning() {
238
  coloured_text "$1" "${COLOUR_WARNING}"
239
}
240
241
# -----------------------------------------------------------------------------
242
# Write an error message to standard output.
243
#
244
# $1 - Text to write
245
# -----------------------------------------------------------------------------
246
error() {
247
  coloured_text "$1" "${COLOUR_ERROR}"
248
}
249
250
# -----------------------------------------------------------------------------
251
# Write a timestamp and message to standard output.
252
#
253
# $1 - Text to write
254
# -----------------------------------------------------------------------------
255
utile_log() {
256
  printf "[%s] " "$(date +%H:%M:%S.%4N)"
257
  coloured_text "$1" "${COLOUR_LOGGING}"
258
}
259
260
# -----------------------------------------------------------------------------
261
# Perform no operations.
262
#
263
# return 1 - Success
264
# -----------------------------------------------------------------------------
265
noop() {
266
  return 1
267
}
268
269
# -----------------------------------------------------------------------------
270
# Exit the program with a given exit code.
271
#
272
# $1 - Exit code
273
# -----------------------------------------------------------------------------
274
terminate() {
275
  exit "$1"
276
}
277
278
# -----------------------------------------------------------------------------
279
# Set global variables from command-line arguments.
280
# -----------------------------------------------------------------------------
281
arguments() {
282
  while [ "$#" -gt "0" ]; do
283
    local consume=1
284
285
    case "$1" in
286
      -V|--verbose)
287
        log=utile_log
288
      ;;
289
      -h|-\?|--help)
290
        usage=utile_usage
291
      ;;
292
      *)
293
        set +e
294
        argument "$@"
295
        consume=$?
296
        set -e
297
      ;;
298
    esac
299
300
    shift ${consume}
301
  done
302
}
303
304
# -----------------------------------------------------------------------------
305
# Parses a single command-line argument. This must return a value greater
306
# than or equal to 1, otherwise parsing the command-line arguments will
307
# loop indefinitely.
308
#
309
# @return The number of arguments to consume (1 by default).
310
# -----------------------------------------------------------------------------
311
argument() {
312
  return 1
313
}
314
315
# ANSI colour escape sequences.
316
readonly COLOUR_BLUE='\033[1;34m'
317
readonly COLOUR_PINK='\033[1;35m'
318
readonly COLOUR_DKGRAY='\033[30m'
319
readonly COLOUR_DKRED='\033[31m'
320
readonly COLOUR_LTRED='\033[1;31m'
321
readonly COLOUR_YELLOW='\033[1;33m'
322
readonly COLOUR_OFF='\033[0m'
323
324
# Colour definitions used by script.
325
COLOUR_LOGGING=${COLOUR_BLUE}
326
COLOUR_WARNING=${COLOUR_YELLOW}
327
COLOUR_ERROR=${COLOUR_LTRED}
328
329
# Define required commands to check when script starts.
330
DEPENDENCIES=(
331
  "awk,https://www.gnu.org/software/gawk/manual/gawk.html"
332
  "cut,https://www.gnu.org/software/coreutils"
333
)
334
335
# Define help for command-line arguments.
336
ARGUMENTS=(
337
  "V,verbose,Log messages while processing"
338
  "h,help,Show this help message then exit"
339
)
340
341
# These functions may be set to utile delegates while parsing arguments.
342
usage=noop
343
log=noop
344
1345
M build.gradle
1
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
2
13
plugins {
24
  id 'application'
...
1618
  maven {
1719
    url "https://nexus.bedatadriven.com/content/groups/public"
20
  }
21
}
22
23
String targetOs
24
25
if (binding.hasVariable('targetOs') && "windows".equals(targetOs)) {
26
  targetOs = "win"
27
} else {
28
  targetOs = "linux"
29
30
  def os = DefaultNativePlatform.currentOperatingSystem
31
32
  if (os.isMacOsX()) {
33
    targetOs = "mac"
34
  } else if (os.isWindows()) {
35
    targetOs = "win"
1836
  }
1937
}
...
84102
  implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3'
85103
86
  def os = ['win', 'linux', 'mac']
87104
  def fx = ['controls', 'graphics', 'fxml', 'swing']
88105
89
  // Create cross-platform überjar.
90
  // Including these runtime dependencies breaks creating cross-platform binaries.
91106
  fx.each { fxitem ->
92
    os.each { ositem ->
93
      runtimeOnly "org.openjfx:javafx-${fxitem}:${javafx.version}:${ositem}"
94
    }
107
    runtimeOnly "org.openjfx:javafx-${fxitem}:${javafx.version}:${targetOs}"
95108
  }
96109
...
154167
      }
155168
    }
156
  }
157
}
158
159
160
jlink {
161
  options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
162
  forceMerge 'jackson'
163
164
  launcher {
165
    name = 'java-keywords'
166
  }
167
168
  addExtraDependencies('javafx')
169
  jpackage {
170
    // Can also set via environment property BADASS_JLINK_JPACKAGE_HOME
171
    jpackageHome = '/opt/jdk'
172
//    jvmArgs = ['-splash:$APPDIR/splash.png']
173
//    imageOptions = ['--icon', 'src/main/resources/java.ico']
174
//    installerOptions = [
175
//        '--file-associations', 'src/main/resources/associations.properties',
176
//        '--app-version', version,
177
//    ]
178
//    if (org.gradle.internal.os.OperatingSystem.current().windows) {
179
//      installerOptions += ['--win-per-user-install', '--win-dir-chooser', '--win-menu']
180
//    }
181
//  }
182169
  }
183170
}
A installer
1
#!/usr/bin/env bash
2
3
# ---------------------------------------------------------------------------
4
# This script cross-compiles application launchers for different platforms.
5
#
6
# The application binaries are self-contained launchers that do not need
7
# to be installed.
8
# ---------------------------------------------------------------------------
9
10
source build-template
11
12
readonly APP_NAME=$(find "${SCRIPT_DIR}/src" -type f -name "settings.properties" -exec cat {} \; | grep "application.title=" | cut -d'=' -f2)
13
readonly FILE_APP_JAR="${APP_NAME}.jar"
14
15
ARG_JRE_OS="linux"
16
ARG_JRE_ARCH="amd64"
17
ARG_JRE_VERSION="14.0.1"
18
ARG_JRE_UPDATE="8"
19
ARG_JRE_DIR="jre"
20
21
ARG_DIR_DIST="dist"
22
23
FILE_DIST_EXEC="run.sh"
24
25
ARG_PATH_DIST_JAR="${SCRIPT_DIR}/build/libs/${FILE_APP_JAR}"
26
27
DEPENDENCIES=(
28
  "gradle,https://gradle.org"
29
  "warp-packer,https://github.com/dgiagio/warp"
30
  "tar,https://www.gnu.org/software/tar"
31
  "unzip,http://infozip.sourceforge.net"
32
)
33
34
ARGUMENTS+=(
35
  "a,arch,Target operating system architecture (amd64)"
36
  "b,build,Suppress building application"
37
  "o,os,Target operating system (linux, windows, mac)"
38
  "u,update,Java update version number (${ARG_JRE_UPDATE})"
39
  "v,version,Full Java version (${ARG_JRE_VERSION})"
40
)
41
42
ARCHIVE_EXT="tar.gz"
43
ARCHIVE_APP="tar xf"
44
APP_EXTENSION="bin"
45
46
# ---------------------------------------------------------------------------
47
# Generates
48
# ---------------------------------------------------------------------------
49
execute() {
50
  $do_configure_target
51
  $do_build
52
  $do_clean
53
54
  pushd "${ARG_DIR_DIST}" > /dev/null 2>&1
55
56
  $do_extract_jre
57
  $do_create_launch_script
58
  $do_copy_archive
59
60
  popd > /dev/null 2>&1
61
62
  $do_create_launcher
63
64
  return 1
65
}
66
67
# ---------------------------------------------------------------------------
68
# Configure platform-specific commands and file names.
69
# ---------------------------------------------------------------------------
70
utile_configure_target() {
71
  if [ "${ARG_JRE_OS}" = "windows" ]; then
72
    ARCHIVE_EXT="zip"
73
    ARCHIVE_APP="unzip -qq"
74
    FILE_DIST_EXEC="run.bat"
75
    APP_EXTENSION="exe"
76
    do_create_launch_script=utile_create_launch_script_windows
77
  fi
78
}
79
80
# ---------------------------------------------------------------------------
81
# Build platform-specific überjar.
82
# ---------------------------------------------------------------------------
83
utile_build() {
84
  $log "Build application for ${ARG_JRE_OS}"
85
  gradle clean jar -PtargetOs="${ARG_JRE_OS}"
86
}
87
88
# ---------------------------------------------------------------------------
89
# Purges the existing distribution directory to recreate the launcher.
90
# This refreshes the JRE from the downloaded archive.
91
# ---------------------------------------------------------------------------
92
utile_clean() {
93
  $log "Recreate ${ARG_DIR_DIST}"
94
  rm -rf "${ARG_DIR_DIST}"
95
  mkdir -p "${ARG_DIR_DIST}"
96
}
97
98
# ---------------------------------------------------------------------------
99
# Extract platform-specific Java Runtime Environment. This will download
100
# and cache the required Java Runtime Environment for the target platform.
101
# On subsequent runs, the cached version is used, instead of issuing another
102
# download.
103
# ---------------------------------------------------------------------------
104
utile_extract_jre() {
105
  $log "Extract JRE"
106
  local -r jre_version="${ARG_JRE_VERSION}+${ARG_JRE_UPDATE}"
107
  local -r url_jdk="https://download.bell-sw.com/java/${jre_version}/bellsoft-jre${jre_version}-${ARG_JRE_OS}-${ARG_JRE_ARCH}-full.${ARCHIVE_EXT}"
108
109
  local -r file_jdk="jre-${jre_version}-${ARG_JRE_OS}-${ARG_JRE_ARCH}.${ARCHIVE_EXT}"
110
  local -r path_jdk="/tmp/${file_jdk}"
111
112
  if [ ! -f ${path_jdk} ]; then
113
    $log "Download ${url_jdk}"
114
    wget -q "${url_jdk}" -O "${path_jdk}"
115
  fi
116
117
  $log "Unpack ${path_jdk}"
118
  $ARCHIVE_APP "${path_jdk}"
119
120
  local -r dir_jdk="jre-${ARG_JRE_VERSION}-full"
121
122
  $log "Rename ${dir_jdk}-jre to ${ARG_JRE_DIR}"
123
  mv "${dir_jdk}" "${ARG_JRE_DIR}"
124
}
125
126
# ---------------------------------------------------------------------------
127
# Create Linux-specific launch script.
128
# ---------------------------------------------------------------------------
129
utile_create_launch_script_linux() {
130
  $log "Create Linux launch script"
131
132
  cat > "${FILE_DIST_EXEC}" << __EOT
133
#!/usr/bin/env bash
134
135
readonly SCRIPT_SRC="\$(dirname "\${BASH_SOURCE[\${#BASH_SOURCE[@]} - 1]}")"
136
readonly SCRIPT_DIR="\$(cd "\${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)"
137
138
"\${SCRIPT_DIR}/${ARG_JRE_DIR}/bin/java" -jar "\${SCRIPT_DIR}/${FILE_APP_JAR}" "\$@"
139
__EOT
140
141
  chmod +x "${FILE_DIST_EXEC}"
142
}
143
144
# ---------------------------------------------------------------------------
145
# Create Windows-specific launch script.
146
# ---------------------------------------------------------------------------
147
utile_create_launch_script_windows() {
148
  $log "Create Windows launch script"
149
150
  cat > "${FILE_DIST_EXEC}" << __EOT
151
@echo off
152
153
set SCRIPT_DIR=%~dp0
154
"%SCRIPT_DIR%jre\\bin\\java" -jar "%SCRIPT_DIR%\\scrivenvar.jar" %*
155
__EOT
156
157
  # Convert Unix end of line characters (\n) to Windows format (\r\n).
158
  # This avoids any potential line conversion issues with the repository.
159
  sed -i 's/$/\r/' "${FILE_DIST_EXEC}"
160
}
161
162
# ---------------------------------------------------------------------------
163
# Copy application überjar.
164
# ---------------------------------------------------------------------------
165
utile_copy_archive() {
166
  $log "Create copy of ${FILE_APP_JAR}"
167
  cp "${ARG_PATH_DIST_JAR}" "${FILE_APP_JAR}"
168
}
169
170
# ---------------------------------------------------------------------------
171
# Create platform-specific launcher binary.
172
# ---------------------------------------------------------------------------
173
utile_create_launcher() {
174
  $log "Create ${APP_NAME}.${APP_EXTENSION}"
175
176
  # Download uses amd64, but warp-packer differs.
177
  if [ "${ARG_JRE_ARCH}" = "amd64" ]; then
178
    ARG_JRE_ARCH="x64"
179
  fi
180
181
  warp-packer \
182
    --arch "${ARG_JRE_OS}-${ARG_JRE_ARCH}" \
183
    --input_dir "${ARG_DIR_DIST}" \
184
    --exec "${FILE_DIST_EXEC}" \
185
    --output "${APP_NAME}.${APP_EXTENSION}" > /dev/null
186
187
  chmod +x "${APP_NAME}.${APP_EXTENSION}"
188
}
189
190
argument() {
191
  local consume=2
192
193
  case "$1" in
194
    -a|--arch)
195
    ARG_JRE_ARCH="$2"
196
    ;;
197
    -b|--build)
198
    do_build=noop
199
    consume=1
200
    ;;
201
    -o|--os)
202
    ARG_JRE_OS="$2"
203
    ;;
204
    -u|--update)
205
    ARG_JRE_UPDATE="$2"
206
    ;;
207
    -v|--version)
208
    ARG_JRE_VERSION="$2"
209
    ;;
210
  esac
211
212
  return ${consume}
213
}
214
215
do_configure_target=utile_configure_target
216
do_build=utile_build
217
do_clean=utile_clean
218
do_extract_jre=utile_extract_jre
219
do_create_launch_script=utile_create_launch_script_linux
220
do_copy_archive=utile_copy_archive
221
do_create_launcher=utile_create_launcher
222
223
main "$@"
224
1225
M src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
5151
import java.awt.event.ComponentEvent;
5252
import java.awt.event.ComponentListener;
53
import java.io.IOException;
5453
import java.net.URI;
55
import java.net.URISyntaxException;
5654
import java.nio.file.Path;
5755
...
150148
  }
151149
150
  /**
151
   * The CSS must be rendered in points (pt) not pixels (px) to avoid blurry
152
   * rendering on some platforms.
153
   */
152154
  private final static String HTML_HEADER = "<!DOCTYPE html>"
153155
      + "<html>"