markymark 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +29 -0
- data/LICENSE.txt +21 -0
- data/README.md +255 -0
- data/Rakefile +8 -0
- data/assets/.gitkeep +0 -0
- data/assets/Markymark.icns +0 -0
- data/assets/Markymark.iconset/icon_128x128.png +0 -0
- data/assets/Markymark.iconset/icon_128x128@2x.png +0 -0
- data/assets/Markymark.iconset/icon_16x16.png +0 -0
- data/assets/Markymark.iconset/icon_16x16@2x.png +0 -0
- data/assets/Markymark.iconset/icon_256x256.png +0 -0
- data/assets/Markymark.iconset/icon_256x256@2x.png +0 -0
- data/assets/Markymark.iconset/icon_32x32.png +0 -0
- data/assets/Markymark.iconset/icon_32x32@2x.png +0 -0
- data/assets/Markymark.iconset/icon_512x512.png +0 -0
- data/assets/Markymark.iconset/icon_512x512@2x.png +0 -0
- data/assets/README.md +3 -0
- data/assets/marky-mark-dj.jpg +0 -0
- data/assets/marky-mark-icon.png +0 -0
- data/assets/marky-mark-icon2.png +0 -0
- data/config.ru +19 -0
- data/docs/for_llms.md +141 -0
- data/docs/plans/2025-12-18-macos-app-installer-design.md +149 -0
- data/exe/markymark +5 -0
- data/lib/markymark/app_installer.rb +437 -0
- data/lib/markymark/cli.rb +497 -0
- data/lib/markymark/init_wizard.rb +186 -0
- data/lib/markymark/pumadev_manager.rb +194 -0
- data/lib/markymark/server_simple.rb +452 -0
- data/lib/markymark/version.rb +5 -0
- data/lib/markymark.rb +12 -0
- data/lib/public/css/style.css +350 -0
- data/lib/public/js/app.js +186 -0
- data/lib/public/js/theme.js +79 -0
- data/lib/public/js/tree.js +124 -0
- data/lib/views/browse.erb +225 -0
- data/lib/views/index.erb +37 -0
- data/lib/views/simple.erb +806 -0
- data/sig/markymark.rbs +4 -0
- metadata +242 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Markymark
|
|
7
|
+
# Installs Markymark as a macOS application for file handling
|
|
8
|
+
class AppInstaller
|
|
9
|
+
APP_NAME = 'Markymark.app'
|
|
10
|
+
BUNDLE_ID = 'com.markymark.app'
|
|
11
|
+
APP_DIR = File.expand_path('~/Applications')
|
|
12
|
+
APP_PATH = File.join(APP_DIR, APP_NAME)
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def install(force: false)
|
|
16
|
+
unless macos?
|
|
17
|
+
warn "App installation is only supported on macOS"
|
|
18
|
+
return false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if File.exist?(APP_PATH) && !force
|
|
22
|
+
print "Markymark.app already exists. Overwrite? [y/N] "
|
|
23
|
+
response = $stdin.gets&.strip&.downcase
|
|
24
|
+
return false unless response == 'y'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "Installing Markymark.app to ~/Applications..."
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
create_app_bundle
|
|
31
|
+
puts "Successfully installed Markymark.app"
|
|
32
|
+
puts "Location: #{APP_PATH}"
|
|
33
|
+
true
|
|
34
|
+
rescue => e
|
|
35
|
+
warn "Failed to install app: #{e.message}"
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def uninstall
|
|
41
|
+
unless macos?
|
|
42
|
+
warn "App uninstallation is only supported on macOS"
|
|
43
|
+
return false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
unless File.exist?(APP_PATH)
|
|
47
|
+
puts "Markymark.app is not installed"
|
|
48
|
+
return true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
puts "Removing Markymark.app..."
|
|
52
|
+
FileUtils.rm_rf(APP_PATH)
|
|
53
|
+
puts "Successfully removed Markymark.app"
|
|
54
|
+
true
|
|
55
|
+
rescue => e
|
|
56
|
+
warn "Failed to uninstall app: #{e.message}"
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_default_handler
|
|
61
|
+
unless macos?
|
|
62
|
+
warn "Setting default handler is only supported on macOS"
|
|
63
|
+
return false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
unless File.exist?(APP_PATH)
|
|
67
|
+
warn "Markymark.app must be installed first. Run: markymark --install-app"
|
|
68
|
+
return false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
puts "Setting Markymark as default handler for .md files..."
|
|
72
|
+
|
|
73
|
+
# Try duti first (more reliable)
|
|
74
|
+
if duti_available?
|
|
75
|
+
return set_default_with_duti
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# duti not installed - offer to install it
|
|
79
|
+
if homebrew_available?
|
|
80
|
+
puts ""
|
|
81
|
+
puts "For automatic default app registration, 'duti' is recommended."
|
|
82
|
+
print "Install duti via Homebrew? [Y/n] "
|
|
83
|
+
response = $stdin.gets&.strip&.downcase
|
|
84
|
+
|
|
85
|
+
if response.nil? || response.empty? || response == 'y' || response == 'yes'
|
|
86
|
+
puts "Installing duti..."
|
|
87
|
+
if system('brew install duti')
|
|
88
|
+
puts "duti installed successfully."
|
|
89
|
+
return set_default_with_duti
|
|
90
|
+
else
|
|
91
|
+
warn "Failed to install duti."
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Fall back to lsregister + manual instructions
|
|
97
|
+
lsregister = '/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister'
|
|
98
|
+
if File.exist?(lsregister)
|
|
99
|
+
# Register the app
|
|
100
|
+
system("#{lsregister} -f '#{APP_PATH}'")
|
|
101
|
+
puts ""
|
|
102
|
+
puts "Registered Markymark.app with Launch Services."
|
|
103
|
+
puts ""
|
|
104
|
+
puts "To complete setup, please manually set Markymark as default:"
|
|
105
|
+
puts " 1. Right-click any .md file in Finder"
|
|
106
|
+
puts " 2. Select 'Get Info'"
|
|
107
|
+
puts " 3. Under 'Open with:', select 'Markymark'"
|
|
108
|
+
puts " 4. Click 'Change All...'"
|
|
109
|
+
return true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
warn "Could not set default handler automatically."
|
|
113
|
+
puts "Please install 'duti' (brew install duti) or set manually via Finder."
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def set_default_with_duti
|
|
118
|
+
success = system("duti -s #{BUNDLE_ID} .md all") &&
|
|
119
|
+
system("duti -s #{BUNDLE_ID} .markdown all")
|
|
120
|
+
if success
|
|
121
|
+
puts "Successfully set Markymark as default handler for .md files"
|
|
122
|
+
return true
|
|
123
|
+
end
|
|
124
|
+
false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def duti_available?
|
|
128
|
+
system('which duti > /dev/null 2>&1')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def homebrew_available?
|
|
132
|
+
system('which brew > /dev/null 2>&1')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def installed?
|
|
136
|
+
File.exist?(APP_PATH)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def status
|
|
140
|
+
return nil unless macos?
|
|
141
|
+
|
|
142
|
+
unless File.exist?(APP_PATH)
|
|
143
|
+
return { installed: false, message: "Markymark.app is not installed" }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
launcher_path = File.join(APP_PATH, 'Contents', 'MacOS', 'markymark-launcher')
|
|
147
|
+
unless File.exist?(launcher_path)
|
|
148
|
+
return { installed: true, valid: false, message: "Markymark.app is corrupted (missing launcher)" }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Check if the baked-in Ruby path still exists (stored in helper script)
|
|
152
|
+
helper_path = File.join(APP_PATH, 'Contents', 'MacOS', 'markymark-helper')
|
|
153
|
+
unless File.exist?(helper_path)
|
|
154
|
+
return { installed: true, valid: false, message: "Markymark.app is corrupted (missing helper)" }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
helper_content = File.read(helper_path)
|
|
158
|
+
ruby_path = helper_content[/RUBY_PATH="([^"]+)"/, 1]
|
|
159
|
+
|
|
160
|
+
if ruby_path && !File.exist?(ruby_path)
|
|
161
|
+
return {
|
|
162
|
+
installed: true,
|
|
163
|
+
valid: false,
|
|
164
|
+
ruby_path: ruby_path,
|
|
165
|
+
message: "Markymark.app needs reinstalling (Ruby path changed: #{ruby_path})"
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
installed: true,
|
|
171
|
+
valid: true,
|
|
172
|
+
ruby_path: ruby_path,
|
|
173
|
+
message: "Markymark.app is installed and valid"
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
def macos?
|
|
180
|
+
RUBY_PLATFORM.include?('darwin')
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def create_app_bundle
|
|
184
|
+
# Create directory structure
|
|
185
|
+
FileUtils.mkdir_p(APP_DIR)
|
|
186
|
+
FileUtils.rm_rf(APP_PATH) if File.exist?(APP_PATH)
|
|
187
|
+
|
|
188
|
+
contents_dir = File.join(APP_PATH, 'Contents')
|
|
189
|
+
macos_dir = File.join(contents_dir, 'MacOS')
|
|
190
|
+
resources_dir = File.join(contents_dir, 'Resources')
|
|
191
|
+
|
|
192
|
+
FileUtils.mkdir_p(macos_dir)
|
|
193
|
+
FileUtils.mkdir_p(resources_dir)
|
|
194
|
+
|
|
195
|
+
# Write Info.plist
|
|
196
|
+
write_info_plist(contents_dir)
|
|
197
|
+
|
|
198
|
+
# Write launcher script
|
|
199
|
+
write_launcher_script(macos_dir)
|
|
200
|
+
|
|
201
|
+
# Copy icon
|
|
202
|
+
copy_icon(resources_dir)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def write_info_plist(contents_dir)
|
|
206
|
+
plist_path = File.join(contents_dir, 'Info.plist')
|
|
207
|
+
|
|
208
|
+
plist_content = <<~PLIST
|
|
209
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
210
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
211
|
+
<plist version="1.0">
|
|
212
|
+
<dict>
|
|
213
|
+
<key>CFBundleName</key>
|
|
214
|
+
<string>Markymark</string>
|
|
215
|
+
<key>CFBundleDisplayName</key>
|
|
216
|
+
<string>Markymark</string>
|
|
217
|
+
<key>CFBundleIdentifier</key>
|
|
218
|
+
<string>#{BUNDLE_ID}</string>
|
|
219
|
+
<key>CFBundleVersion</key>
|
|
220
|
+
<string>#{Markymark::VERSION}</string>
|
|
221
|
+
<key>CFBundleShortVersionString</key>
|
|
222
|
+
<string>#{Markymark::VERSION}</string>
|
|
223
|
+
<key>CFBundlePackageType</key>
|
|
224
|
+
<string>APPL</string>
|
|
225
|
+
<key>CFBundleExecutable</key>
|
|
226
|
+
<string>markymark-launcher</string>
|
|
227
|
+
<key>CFBundleIconFile</key>
|
|
228
|
+
<string>Markymark</string>
|
|
229
|
+
<key>LSMinimumSystemVersion</key>
|
|
230
|
+
<string>10.13</string>
|
|
231
|
+
<key>NSHighResolutionCapable</key>
|
|
232
|
+
<true/>
|
|
233
|
+
<key>CFBundleDocumentTypes</key>
|
|
234
|
+
<array>
|
|
235
|
+
<dict>
|
|
236
|
+
<key>CFBundleTypeName</key>
|
|
237
|
+
<string>Markdown Document</string>
|
|
238
|
+
<key>CFBundleTypeRole</key>
|
|
239
|
+
<string>Viewer</string>
|
|
240
|
+
<key>LSHandlerRank</key>
|
|
241
|
+
<string>Default</string>
|
|
242
|
+
<key>LSItemContentTypes</key>
|
|
243
|
+
<array>
|
|
244
|
+
<string>net.daringfireball.markdown</string>
|
|
245
|
+
<string>public.text</string>
|
|
246
|
+
</array>
|
|
247
|
+
<key>CFBundleTypeExtensions</key>
|
|
248
|
+
<array>
|
|
249
|
+
<string>md</string>
|
|
250
|
+
<string>markdown</string>
|
|
251
|
+
<string>mdown</string>
|
|
252
|
+
<string>mkd</string>
|
|
253
|
+
</array>
|
|
254
|
+
</dict>
|
|
255
|
+
</array>
|
|
256
|
+
<key>UTImportedTypeDeclarations</key>
|
|
257
|
+
<array>
|
|
258
|
+
<dict>
|
|
259
|
+
<key>UTTypeIdentifier</key>
|
|
260
|
+
<string>net.daringfireball.markdown</string>
|
|
261
|
+
<key>UTTypeDescription</key>
|
|
262
|
+
<string>Markdown Document</string>
|
|
263
|
+
<key>UTTypeConformsTo</key>
|
|
264
|
+
<array>
|
|
265
|
+
<string>public.plain-text</string>
|
|
266
|
+
</array>
|
|
267
|
+
<key>UTTypeTagSpecification</key>
|
|
268
|
+
<dict>
|
|
269
|
+
<key>public.filename-extension</key>
|
|
270
|
+
<array>
|
|
271
|
+
<string>md</string>
|
|
272
|
+
<string>markdown</string>
|
|
273
|
+
<string>mdown</string>
|
|
274
|
+
<string>mkd</string>
|
|
275
|
+
</array>
|
|
276
|
+
</dict>
|
|
277
|
+
</dict>
|
|
278
|
+
</array>
|
|
279
|
+
</dict>
|
|
280
|
+
</plist>
|
|
281
|
+
PLIST
|
|
282
|
+
|
|
283
|
+
File.write(plist_path, plist_content)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def write_launcher_script(macos_dir)
|
|
287
|
+
ruby_path = which_ruby
|
|
288
|
+
markymark_path = which_markymark
|
|
289
|
+
|
|
290
|
+
# Write the shell script helper
|
|
291
|
+
gem_home = ENV['GEM_HOME'] || Gem.dir
|
|
292
|
+
gem_path = ENV['GEM_PATH'] || Gem.path.join(':')
|
|
293
|
+
markymark_lib = File.dirname(File.dirname(markymark_path))
|
|
294
|
+
|
|
295
|
+
helper_path = File.join(macos_dir, 'markymark-helper')
|
|
296
|
+
helper_content = <<~SCRIPT
|
|
297
|
+
#!/bin/bash
|
|
298
|
+
# Markymark helper - called by the Swift launcher
|
|
299
|
+
|
|
300
|
+
RUBY_PATH="#{ruby_path}"
|
|
301
|
+
MARKYMARK_PATH="#{markymark_path}"
|
|
302
|
+
GEM_HOME="#{gem_home}"
|
|
303
|
+
GEM_PATH="#{gem_path}"
|
|
304
|
+
MARKYMARK_LIB="#{markymark_lib}/lib"
|
|
305
|
+
LOG_FILE="$HOME/.markymark/launcher.log"
|
|
306
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
307
|
+
echo "$(date): Helper called with: $@" >> "$LOG_FILE"
|
|
308
|
+
|
|
309
|
+
# Check if Ruby still exists
|
|
310
|
+
if [ ! -f "$RUBY_PATH" ]; then
|
|
311
|
+
osascript -e 'display alert "Markymark Error" message "Ruby installation has changed. Please run: markymark --install-app" as critical'
|
|
312
|
+
exit 1
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
# Check if markymark still exists
|
|
316
|
+
if [ ! -f "$MARKYMARK_PATH" ]; then
|
|
317
|
+
osascript -e 'display alert "Markymark Error" message "Markymark gem not found. Please run: gem install markymark && markymark --install-app" as critical'
|
|
318
|
+
exit 1
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# Set up gem environment and launch markymark
|
|
322
|
+
export GEM_HOME="$GEM_HOME"
|
|
323
|
+
export GEM_PATH="$GEM_PATH"
|
|
324
|
+
export RUBYLIB="$MARKYMARK_LIB:$RUBYLIB"
|
|
325
|
+
"$RUBY_PATH" "$MARKYMARK_PATH" "$@" >> "$LOG_FILE" 2>&1 &
|
|
326
|
+
SCRIPT
|
|
327
|
+
File.write(helper_path, helper_content)
|
|
328
|
+
FileUtils.chmod(0o755, helper_path)
|
|
329
|
+
|
|
330
|
+
# Compile a Swift binary that properly receives files from Launch Services
|
|
331
|
+
# AppleScript droplets don't work with macOS `open` command (used by iTerm cmd-click)
|
|
332
|
+
write_swift_launcher(macos_dir, helper_path)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def write_swift_launcher(macos_dir, helper_path)
|
|
336
|
+
# Check if Swift compiler is available
|
|
337
|
+
unless system('which swiftc > /dev/null 2>&1')
|
|
338
|
+
raise "Swift compiler not found. Install Xcode Command Line Tools with: xcode-select --install"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
swift_source = <<~SWIFT
|
|
342
|
+
import Cocoa
|
|
343
|
+
|
|
344
|
+
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
345
|
+
let helperPath = "#{helper_path}"
|
|
346
|
+
var hasOpenedFile = false
|
|
347
|
+
|
|
348
|
+
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
349
|
+
// Give time for openFile to be called first
|
|
350
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
351
|
+
if !self.hasOpenedFile {
|
|
352
|
+
// Launched without files - open home directory
|
|
353
|
+
self.openFile(path: NSHomeDirectory())
|
|
354
|
+
}
|
|
355
|
+
NSApp.terminate(nil)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
func application(_ sender: NSApplication, openFile filename: String) -> Bool {
|
|
360
|
+
hasOpenedFile = true
|
|
361
|
+
openFile(path: filename)
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
func openFile(path: String) {
|
|
366
|
+
let task = Process()
|
|
367
|
+
task.executableURL = URL(fileURLWithPath: helperPath)
|
|
368
|
+
task.arguments = [path]
|
|
369
|
+
try? task.run()
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let app = NSApplication.shared
|
|
374
|
+
let delegate = AppDelegate()
|
|
375
|
+
app.delegate = delegate
|
|
376
|
+
app.run()
|
|
377
|
+
SWIFT
|
|
378
|
+
|
|
379
|
+
# Write Swift source to temp file
|
|
380
|
+
temp_swift = File.join(Dir.tmpdir, 'markymark_launcher.swift')
|
|
381
|
+
File.write(temp_swift, swift_source)
|
|
382
|
+
|
|
383
|
+
# Compile Swift binary
|
|
384
|
+
launcher_path = File.join(macos_dir, 'markymark-launcher')
|
|
385
|
+
success = system("swiftc -o '#{launcher_path}' '#{temp_swift}' -framework Cocoa 2>&1")
|
|
386
|
+
|
|
387
|
+
unless success
|
|
388
|
+
File.delete(temp_swift) if File.exist?(temp_swift)
|
|
389
|
+
raise "Failed to compile Swift launcher"
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
FileUtils.chmod(0o755, launcher_path)
|
|
393
|
+
File.delete(temp_swift) if File.exist?(temp_swift)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def copy_icon(resources_dir)
|
|
397
|
+
# Find the icon in the gem's assets
|
|
398
|
+
gem_root = File.expand_path('../../..', __FILE__)
|
|
399
|
+
icon_source = File.join(gem_root, 'assets', 'Markymark.icns')
|
|
400
|
+
|
|
401
|
+
unless File.exist?(icon_source)
|
|
402
|
+
warn "Warning: Icon file not found at #{icon_source}"
|
|
403
|
+
return
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
icon_dest = File.join(resources_dir, 'Markymark.icns')
|
|
407
|
+
FileUtils.cp(icon_source, icon_dest)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def which_ruby
|
|
411
|
+
# Get the full path to the current Ruby
|
|
412
|
+
RbConfig.ruby
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def which_markymark
|
|
416
|
+
# Find the actual executable inside the gem, not the wrapper script
|
|
417
|
+
# RVM/rbenv wrappers don't work when launched from outside their environment
|
|
418
|
+
begin
|
|
419
|
+
spec = Gem::Specification.find_by_name('markymark')
|
|
420
|
+
exe_path = File.join(spec.gem_dir, 'exe', 'markymark')
|
|
421
|
+
return exe_path if File.exist?(exe_path)
|
|
422
|
+
rescue Gem::MissingSpecError
|
|
423
|
+
# Gem not found via spec
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Fallback: try to find via gem contents
|
|
427
|
+
contents = `gem contents markymark 2>/dev/null`.strip
|
|
428
|
+
contents.each_line do |line|
|
|
429
|
+
return line.strip if line.include?('/exe/markymark')
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Last resort fallback
|
|
433
|
+
'/usr/local/bin/markymark'
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|