浏览代码

Initial commit

master
当前提交
6a96167e75
共有 12 个文件被更改,包括 499 次插入0 次删除
  1. 11
    0
      Project.toml
  2. 3
    0
      build/Project.toml
  3. 38
    0
      build/make.jl
  4. 39
    0
      build/precomp.jl
  5. 28
    0
      main.jl
  6. 15
    0
      resources/load.html
  7. 78
    0
      resources/spinning_wheel.js
  8. 45
    0
      resources/style.css
  9. 26
    0
      resources/view.html
  10. 3
    0
      run/Project.toml
  11. 30
    0
      run/jld2view
  12. 183
    0
      src/JLD2View.jl

+ 11
- 0
Project.toml 查看文件

@@ -0,0 +1,11 @@
name = "JLD2View"
uuid = "a494b24a-6d3b-4871-b66b-22839c4c06d4"
authors = ["francois "]
version = "0.1.0"

[deps]
Electron = "a1bb12fb-d4d1-54b4-b10a-ee7951ef7ad3"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
NativeFileDialog = "e1fe445b-aa65-4df4-81c1-2041507f0fd4"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"

+ 3
- 0
build/Project.toml 查看文件

@@ -0,0 +1,3 @@
[deps]
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"

+ 38
- 0
build/make.jl 查看文件

@@ -0,0 +1,38 @@
using Pkg; Pkg.activate(@__DIR__); Pkg.instantiate()
using PackageCompiler

using Scratch

Pkg.activate(joinpath(@__DIR__, ".."))
using JLD2View
scratchdir = get_scratch!(JLD2View, "sysimages")

# List of packages to include in the sysimage
packages = :JLD2View #Symbol.(keys(Pkg.project().dependencies))

# Sysimage base name
sysimage_name = "sysimage"

# Sysimage extension
sysimage_ext = if Sys.iswindows()
".dll"
elseif Sys.isapple()
".dylib"
else
".so"
end

sysimage_path = joinpath(scratchdir, sysimage_name * sysimage_ext)

@info("Creating system image",
name=sysimage_path,
packages)

create_sysimage(
packages,
sysimage_path = sysimage_path,
precompile_execution_file = joinpath(@__DIR__, "precomp.jl"),

# Optional: generate a "portable" sysimage on x86-64 architectures
cpu_target = "generic;sandybridge,-xsaveopt,clone_all;haswell,-rdrnd,base(1)",
)

+ 39
- 0
build/precomp.jl 查看文件

@@ -0,0 +1,39 @@
using JLD2
using JLD2View
using JLD2View: send_signal

mktempdir() do path
d = Dict("int" => 1,
"float" => 2.0,
"vector" => rand(1000),
"matrix" => rand(1000, 1000))
d["dict"] = deepcopy(d)
d["array"] = [deepcopy(d), deepcopy(d)]
fname = joinpath(path, "tmp.jld2")
JLD2.save(fname, d)

(viewer, task) = jld2_view(fname, sync=false)
sleep(1)

function simulate(action, arg)
println("Simulating action: $action $arg")
send_signal(viewer, action, arg)
sleep(2)
end

simulate(:select_key, "int")
simulate(:select_key, "float")
simulate(:select_key, "vector"); simulate(:back, 0)
simulate(:select_key, "matrix"); simulate(:back, 0)
simulate(:select_key, "dict"); begin
simulate(:select_key, "vector"); simulate(:back, 1)
simulate(:select_key, "matrix"); simulate(:back, 1)
end; simulate(:back, 0)
simulate(:select_key, "array"); begin
simulate(:select_key, 1); simulate(:back, 1)
end; simulate(:back, 0)

println("Terminating event loop")
schedule(task, :stop, error=true)
wait(task)
end

+ 28
- 0
main.jl 查看文件

@@ -0,0 +1,28 @@
import Pkg; Pkg.activate(@__DIR__, io=IOBuffer())
using JLD2View
using NativeFileDialog

path = get(ARGS, 1) do
pick_file(filterlist="jld2")
end

const HELP = """
Usage:
jld2view [JLD2_PATH]

Display the contents of a JLD2 file. If no file is provided in the command line,
a file-picker dialog box will appear to select one interactively.
"""

if path == "--help"
println(HELP)
exit(0)
elseif path == "" # pick_file cancelled
exit(0)
elseif isfile(path)
jld2_view(path)
else
@error "File not found" path
println("\n", HELP)
exit(1)
end

+ 15
- 0
resources/load.html 查看文件

@@ -0,0 +1,15 @@
<html>
<head>
<title>JLD2view</title>
<style>{{:STYLE}}</style>
</head>
<body>
Loading file `{{:FNAME}}`...

<script>
function showLoading() {
true;
}
</script>
</body>
</html>

+ 78
- 0
resources/spinning_wheel.js 查看文件

@@ -0,0 +1,78 @@
// Author: Jared Goodwin
// showLoading() - Display loading wheel.
// Requires ECMAScript 6 (any modern browser).

function showLoading() {
if (document.getElementById("divLoadingFrame") != null) {
return;
}
var style = document.createElement("style");
style.id = "styleLoadingWindow";
style.innerHTML = `
.loading-frame {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 4;
}

.loading-track {
height: 50px;
display: inline-block;
position: absolute;
top: calc(50% - 50px);
left: 50%;
}

.loading-dot {
height: 5px;
width: 5px;
background-color: white;
border-radius: 100%;
opacity: 0;
}

.loading-dot-animated {
animation-name: loading-dot-animated;
animation-direction: alternate;
animation-duration: .75s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}

@keyframes loading-dot-animated {
from {
opacity: 0;
}

to {
opacity: 1;
}
}
`
document.body.appendChild(style);
var frame = document.createElement("div");
frame.id = "divLoadingFrame";
frame.classList.add("loading-frame");
for (var i = 0; i < 10; i++) {
var track = document.createElement("div");
track.classList.add("loading-track");
var dot = document.createElement("div");
dot.classList.add("loading-dot");
track.style.transform = "rotate(" + String(i * 36) + "deg)";
track.appendChild(dot);
frame.appendChild(track);
}
document.body.appendChild(frame);
var wait = 0;
var dots = document.getElementsByClassName("loading-dot");
for (var i = 0; i < dots.length; i++) {
window.setTimeout(function(dot) {
dot.classList.add("loading-dot-animated");
}, wait, dots[i]);
wait += 150;
}
}

+ 45
- 0
resources/style.css 查看文件

@@ -0,0 +1,45 @@
#jld2 {
padding-left: 1em;
padding-top: 0.5em;
}

#jld2 td {
padding-top: 0.25em;
padding-bottom: 0.25em;
padding-left: 0.5em;
padding-right: 0.5em;
}

#jld2 tr {
cursor: pointer;
}

#jld2 tr:hover td {
background: lightblue;
}

#warning {
padding: 0.75em 1em;
border-radius: 4px;
border-style: solid;
border-width: 1px;
margin-top: 0.5em;

background-color: rgba(252, 248, 227, 1);
border-color: rgba(177, 161, 129, 1);
color: rgba(138, 109, 59, 1);
}

a {
text-decoration: none;
color: black;
}

.key {
font-weight: bold;
font-family: monospace;
}

.value {
font-family: monospace;
}

+ 26
- 0
resources/view.html 查看文件

@@ -0,0 +1,26 @@
<html>
<head>
<title>JLD2view</title>
<style>{{{:STYLE}}}</style>
<script>{{{:SPIN_WHL}}}</script>
</head>
<body>
<a class="key" href="#" onclick='sendMessageToJulia("back|0")'>[{{:FNAME}}]</a>
{{#:PATH}}
⊳ <a class="key" href="#" onclick='sendMessageToJulia("back|{{:idx}}")'>{{:name}}</a>
{{/:PATH}}

{{#:WARNING}}
<div id="warning"><b>WARNING:</b> {{:text}}</div>
{{/:WARNING}}

<table id="jld2" cellspacing="0">
{{#:DATA}}
<tr onclick='sendMessageToJulia("select|{{:idx}}")'>
<td><span class="key">⊳ {{:key}}</span></td>
<td><span class="value">{{:val}}</span></td>
</tr>
{{/:DATA}}
</table>
</body>
</html>

+ 3
- 0
run/Project.toml 查看文件

@@ -0,0 +1,3 @@
[deps]
JLD2View = "a494b24a-6d3b-4871-b66b-22839c4c06d4"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"

+ 30
- 0
run/jld2view 查看文件

@@ -0,0 +1,30 @@
#!/bin/bash
#=
exec julia --color=no --startup-file=no --compile=min --optimize=0 "${BASH_SOURCE[0]}" "$@"
=#

using Pkg
Pkg.activate(@__DIR__, io=IOBuffer())
using Scratch

julia = Base.julia_cmd()[1]
script = joinpath(@__DIR__, "..", "main.jl")
args = String["--compile=min", "--optimize=0"]

scratchdir = get_scratch!(Pkg.project().dependencies["JLD2View"], "sysimages")
sysimage = joinpath(scratchdir, "sysimage.so")
if (ispath(sysimage))
push!(args, "--sysimage=$sysimage")
end

cmd = `$julia $args $script $ARGS`
try
run(cmd)
catch e
process = first(e.procs)
exit(process.exitcode)
end

# Local Variables:
# mode: julia
# End:

+ 183
- 0
src/JLD2View.jl 查看文件

@@ -0,0 +1,183 @@
module JLD2View

using Electron
using Mustache
using JLD2
using Printf

export jld2_view

function pretty_repr(x)
io = IOBuffer()
show(IOContext(io, :compact=>true, :limit=>true), "text/plain", x)
String(take!(io))
end

function pretty_repr(x::Vector)
n = length(x)
if n < 10 && all(e isa Number for e in x)
"[" * join((pretty_repr(e) for e in x), ", ") * "]"
else
"<$n-element Vector>"
end
end

function pretty_repr(x::Matrix)
m, n = size(x)
"<$m×$n Matrix>"
end

function pretty_repr(x::Dict)
n = length(keys(x))
"<$n-entry Dict>"
end

const STYLE = String(read( joinpath(@__DIR__, "..", "resources", "style.css")))
const SPIN_WHL = String(read( joinpath(@__DIR__, "..", "resources", "spinning_wheel.js")))
const PAGE_LOAD = Mustache.load(joinpath(@__DIR__, "..", "resources", "load.html"))
const PAGE_VIEW = Mustache.load(joinpath(@__DIR__, "..", "resources", "view.html"))

mutable struct Viewer
app
win

fname
data
path
keys

function Viewer(path)
viewer = new()
viewer.fname = last(splitpath(path))

create_win!(viewer)

@info "Loading JLD2 file" path
@time viewer.data = JLD2.load(path)

update!(viewer, ())
end
end

function get_data(viewer, path=viewer.path)
f(dict, ::Tuple{}) = dict
f(dict, path) = f(dict[first(path)], Base.tail(path))
return f(viewer.data, path)
end

function create_win!(viewer)
viewer.app = Application()
viewer.win = Window(viewer.app, render(PAGE_LOAD, STYLE=STYLE, FNAME=viewer.fname))
end

function update!(viewer, path)
Electron.run(viewer.win, "setTimeout(showLoading, 100)")
viewer.path = path

@info "Refreshing view" path = join(("⌂", path...), " ⊳ ")
@time begin
oldpath = path
warning = nothing

keys = Base.keys(get_data(viewer))
N = 1000
if length(keys) > N
warning = (; text="showing only the first $N entries (out of $(length(keys)))")
keys = Iterators.take(keys, N)
end
viewer.keys = keys = collect(keys)

values = map(keys) do key
pretty_repr(get_data(viewer, (path..., key)))
end

data = [(idx=i, key=keys[i], val=values[i]) for i in eachindex(keys)]
html = render(PAGE_VIEW; STYLE, SPIN_WHL,
DATA = data,
FNAME = viewer.fname,
PATH = [(idx=i, name=x) for (i,x) in enumerate(path)],
WARNING = warning)
open("index.html", "w") do f
write(f, html)
end
Electron.load(viewer.win, html)
end
return viewer
end

function run!(viewer)
if !viewer.app.exists
@warn "Window does not exist any more. Creating a new one."
create_win!(viewer)
update!(viewer, ())
end

ch = msgchannel(viewer.win)

try
while true
msg = try
take!(ch)
catch e
if isa(e, InvalidStateException) && e.state === :closed
@info "Window closed. Terminating"
break
else
rethrow()
end
end

action, arg = split(msg, "|")
arg = parse(Int, arg)
path = if action == "back"
viewer.path[1:arg]
elseif action == "select"
(viewer.path..., viewer.keys[arg])
end

if any(typeof(get_data(viewer, path)) .<: (Dict, AbstractArray))
update!(viewer, path)
end
end
finally
close(viewer.app)
sleep(0.1)
end
end

send_signal(viewer, action::Symbol, args...) = send_signal(viewer, Val(action), args...)

function send_signal(viewer, ::Val{:back}, idx::Integer)
Electron.run(viewer.win, "sendMessageToJulia('back|$idx')")
end

function send_signal(viewer, ::Val{:select}, idx::Integer)
Electron.run(viewer.win, "sendMessageToJulia('select|$idx')")
end

function send_signal(viewer, ::Val{:select_key}, key)
idx = findfirst(==(key), viewer.keys)
@assert !isnothing(idx)
send_signal(viewer, :select, idx)
end

function jld2_view(fname; sync=true)
viewer = Viewer(fname)

task = @async try
run!(viewer)
catch e
e == :stop || rethrow()
end

sync && wait(task)
return (viewer, task)
end

function install()
julia = first(Base.julia_cmd())
make = joinpath(@__DIR__, "..", "build", "make.jl")
run(`$julia $make`)
end

end # module

正在加载...
取消
保存