heze 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '08aae4cc549ba3007b6d1f5bed4c0fc64ccef62dc7b88b5adb0e0d87ae6e0af5'
4
+ data.tar.gz: 12ce5dc54aa66ad197369df743c238ebf70a80784c89eb0067299dbe7edeb517
5
+ SHA512:
6
+ metadata.gz: 7e3907c31c804585c000bcadd82c4c2837741c62ea915364dada9b7e8d53a2fac9ca43745da8e6a266764e221478ec2f9c6bae01cc93aa2a1e829b0c8bfa98b7
7
+ data.tar.gz: 3dc9ade650d3252e369157df155c04570c19841aae9b1b52852e84c1b05f49799ae958879dc3148058c6b09e69792a9d5f637691bd37c5a500ac9c71e0296615
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-09-20
4
+
5
+ - Initial release.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Yudai Takada
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Heze
2
+
3
+ Headless Markdown/SVG preview parsing for the noxdea toolchain. Heze keeps the
4
+ document tree independent from the renderer and exports deterministic PNGs.
5
+
6
+ ## Installation
7
+
8
+ ```sh
9
+ gem install heze
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```sh
15
+ heze README.md --export preview.png
16
+ heze docs/ --width 1200 --height 800 --export preview.png
17
+ heze README.md --backend headless --no-watch
18
+ ```
19
+
20
+ `Heze::Markdown.parse` returns an immutable document tree. SVG input is
21
+ validated before being handed to Zaniah. Headings, paragraphs, lists, quotes,
22
+ tables, fenced code, links, images, and horizontal rules are preserved in the
23
+ preview tree; unsupported nodes remain visible as plain text. Native preview
24
+ falls back to a textual view when the platform backend is unavailable.
25
+
26
+ ## Development
27
+
28
+ Run `rake spec` and `gem build --strict heze.gemspec`.
29
+
30
+ ## Contributing
31
+
32
+ Bug reports and pull requests are welcome at https://github.com/noxdea/heze.
33
+
34
+ ## License
35
+
36
+ MIT.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/exe/heze ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+ require "heze"
6
+ exit Heze::CLI.run(ARGV)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heze
4
+ VERSION = "0.1.0"
5
+ end
data/lib/heze.rb ADDED
@@ -0,0 +1,403 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "pathname"
5
+ require "antares"
6
+ require "kramdown"
7
+ require "kramdown-parser-gfm"
8
+ require "zaniah"
9
+ require "zaniah/ui"
10
+ begin
11
+ require "auva"
12
+ rescue LoadError
13
+ end
14
+ begin
15
+ require "menkar"
16
+ rescue LoadError
17
+ # The fallback keeps the parser useful while an optional local Menkar checkout is used.
18
+ end
19
+ require_relative "heze/version"
20
+
21
+ module Heze
22
+ class Error < StandardError; end
23
+ Node = Data.define(:type, :text, :children, :attributes)
24
+
25
+ module Source
26
+ module_function
27
+
28
+ def read(path)
29
+ bytes = File.binread(path)
30
+ if defined?(Menkar)
31
+ detection = Menkar.detect(bytes)
32
+ raise Error, "binary input: #{path}" if detection.binary
33
+ return japanese_fallback(bytes, Menkar.decode(bytes, detection), detection).delete_prefix("\uFEFF")
34
+ end
35
+ bytes.force_encoding(Encoding::UTF_8)
36
+ raise Error, "invalid UTF-8 input: #{path}" unless bytes.valid_encoding?
37
+ bytes.delete_prefix("\uFEFF")
38
+ rescue Errno::ENOENT
39
+ raise Error, "file not found: #{path}"
40
+ end
41
+
42
+ def japanese_fallback(bytes, text, detection)
43
+ return text unless detection.encoding == Encoding::GBK
44
+
45
+ candidate = bytes.dup.force_encoding(Encoding::Windows_31J).encode(Encoding::UTF_8)
46
+ japanese_score(candidate) > japanese_score(text) ? candidate : text
47
+ rescue EncodingError
48
+ text
49
+ end
50
+
51
+ def japanese_score(text)
52
+ text.scan(/[ぁ-ゟ゠-ヿ]/).length
53
+ end
54
+ private_class_method :japanese_fallback, :japanese_score
55
+ end
56
+
57
+ module Markdown
58
+ module_function
59
+
60
+ def parse(text)
61
+ text = text.to_s.delete_prefix("\uFEFF")
62
+ document = Kramdown::Document.new(text, input: "GFM")
63
+ Node.new(type: :document, text: nil, children: document.root.children.map { |node| map(node) }, attributes: {})
64
+ rescue StandardError => error
65
+ raise Error, "markdown parse failed: #{error.message}"
66
+ end
67
+
68
+ def map(node)
69
+ children = node.children.map { |child| map(child) }
70
+ text = case node.type
71
+ when :text, :codeblock, :codespan then node.value.to_s
72
+ when :a then "#{inline_text(node)} (#{node.attr["href"]})"
73
+ when :img then "[image: #{node.attr["alt"] || node.attr["src"]}]"
74
+ when :header then "#{"#" * node.options.fetch(:level, 1)} #{inline_text(node)}"
75
+ when :ul then node.children.map { |child| "• #{inline_text(child)}" }.join("\n")
76
+ when :ol then node.children.map.with_index { |child, index| "#{index + 1}. #{inline_text(child)}" }.join("\n")
77
+ when :blockquote then node.children.map { |child| "│ #{inline_text(child)}" }.join("\n")
78
+ when :table
79
+ node.children.flat_map { |section| section.children }.map { |row| row.children.map { |cell| inline_text(cell) }.join(" | ") }.join("\n")
80
+ when :hr then "---"
81
+ when :blank then ""
82
+ when :li, :p, :td, :th then inline_text(node)
83
+ else inline_text(node)
84
+ end
85
+ attributes = node.attr.dup
86
+ attributes["language"] = node.options[:lang] if node.type == :codeblock && node.options[:lang]
87
+ Node.new(type: node.type, text: text, children: children, attributes: attributes)
88
+ end
89
+
90
+ def inline_text(node)
91
+ return node.value.to_s if node.children.empty? && node.respond_to?(:value)
92
+ node.children.map { |child| map(child).text.to_s }.join
93
+ end
94
+ private_class_method :inline_text
95
+ end
96
+
97
+ module Highlight
98
+ module_function
99
+
100
+ def tokens(node)
101
+ return [] unless node&.type == :codeblock
102
+ require "antares"
103
+ require "rouge"
104
+ lexer = Rouge::Lexer.find_fancy(node.attributes["language"].to_s)
105
+ lines = node.text.to_s.lines
106
+ highlighter = Antares::Highlighter.new(lexer: lexer, lines: ->(index) { lines[index] }, line_count: -> { lines.length })
107
+ highlighter.tokens_in(0...lines.length)
108
+ rescue LoadError, Rouge::Guesser::Ambiguous
109
+ node.text.to_s.lines.map { |line| [[nil, line]] }
110
+ rescue StandardError
111
+ node.text.to_s.lines.map { |line| [[nil, line]] }
112
+ end
113
+ end
114
+
115
+ module SVG
116
+ module_function
117
+
118
+ def parse(path)
119
+ source = File.binread(path)
120
+ raise Error, "unsupported SVG feature: #{Regexp.last_match(1)}" if source.match(/<(filter|mask|text|linearGradient|radialGradient)\b/i)
121
+ Zaniah::SVG.open(path)
122
+ rescue ArgumentError => error
123
+ raise Error, error.message
124
+ rescue Errno::ENOENT
125
+ raise Error, "SVG file not found: #{path}"
126
+ end
127
+ end
128
+
129
+ module Theme
130
+ module_function
131
+
132
+ def resolve(value)
133
+ return value if value.respond_to?(:colors)
134
+ return Auva.load(value) if defined?(Auva) && File.file?(value.to_s)
135
+ return Auva.builtin(value) if defined?(Auva)
136
+ Zaniah::Theme.public_send(value.to_s)
137
+ rescue StandardError
138
+ raise Error, "unknown theme: #{value}"
139
+ end
140
+ end
141
+
142
+ module Directory
143
+ module_function
144
+
145
+ def markdown(path) = Dir[File.join(path, "**/*.md")].sort
146
+ end
147
+
148
+ class Renderer
149
+ def initialize(theme: Zaniah::Theme.dark, width: 900, height: 1000)
150
+ @theme, @width, @height = theme, width, height
151
+ @font_db = Zaniah::TextSystem::FontDB.new(paths: [])
152
+ @font = @font_db.find(family: theme.typography.font_sans)
153
+ @text_system = Zaniah::TextSystem::Renderer.new(font: @font, font_db: @font_db)
154
+ rescue StandardError
155
+ @font_db = @font = @text_system = nil
156
+ end
157
+
158
+ def render(document, base_path: nil)
159
+ app = Zaniah::App.new
160
+ window = app.open_window(backend: :headless, width: @width, height: @height)
161
+ app.global(:theme, @theme)
162
+ window.text_system = @text_system if @text_system
163
+ window.draw { element(document, base_path: base_path).first }
164
+ window.tick
165
+ device = window.device
166
+ Zaniah::PNG.encode(device.width.to_i, device.height.to_i, device.pixels)
167
+ ensure
168
+ window&.close
169
+ end
170
+
171
+ def show(document, backend: :auto, title: "Heze", watcher: nil, loader: nil, files: nil, selected_path: nil)
172
+ selected = backend == :auto ? (RUBY_PLATFORM.include?("darwin") ? :mac : RUBY_PLATFORM.match?(/mswin|mingw/) ? :windows : :linux) : backend
173
+ app = Zaniah::App.new
174
+ window = app.open_window(backend: selected, width: @width, height: @height, title: title)
175
+ app.global(:theme, @theme)
176
+ window.text_system = @text_system if @text_system
177
+ current = document
178
+ error = nil
179
+ current_path = selected_path
180
+ scroll_view = nil
181
+ load_document = lambda do |path|
182
+ loader.parameters.empty? ? loader.call : loader.call(path)
183
+ end if loader
184
+ window.draw do
185
+ offset = scroll_view&.scroll_state&.offset
186
+ view, next_scroll_view = element(current, files: files, selected_path: current_path,
187
+ base_path: current_path && File.dirname(current_path), on_select: lambda do |path|
188
+ begin
189
+ current = load_document.call(path)
190
+ current_path = path
191
+ watcher = Watcher.new(path)
192
+ error = nil
193
+ rescue StandardError => load_error
194
+ error = load_error
195
+ end
196
+ window.request_frame
197
+ end)
198
+ next_scroll_view.scroll_state.offset = offset if offset && next_scroll_view
199
+ scroll_view = next_scroll_view
200
+ error ? view.child(Zaniah::Text.new("heze: #{error.message}", size: 16, color: @theme.colors.danger)) : view
201
+ end
202
+ window.on_tick do
203
+ next unless watcher&.poll
204
+ if watcher.error
205
+ error = watcher.error
206
+ elsif loader
207
+ begin
208
+ current = load_document.call(current_path)
209
+ error = nil
210
+ rescue StandardError => load_error
211
+ error = load_error
212
+ end
213
+ end
214
+ window.request_frame
215
+ end
216
+ window.run
217
+ ensure
218
+ window&.close
219
+ end
220
+
221
+ private
222
+
223
+ def element(document, files: nil, selected_path: nil, base_path: nil, on_select: nil)
224
+ if document.is_a?(Zaniah::SVG)
225
+ root = Zaniah::Div.new.p(32).bg(@theme.colors.background).child(document)
226
+ [root, nil]
227
+ else
228
+ nodes = document.children.first(50_000)
229
+ scroll = Zaniah::List.new(count: nodes.length, estimated_height: 40) do |index|
230
+ Zaniah::Div.new.p([7, 0]).child(render_node(nodes[index], base_path: base_path))
231
+ end.flex_1
232
+ return [Zaniah::Div.new.flex_col.bg(@theme.colors.background).child(scroll), scroll] unless files&.any?
233
+
234
+ sidebar = Zaniah::UI::Sidebar.new(width: 240)
235
+ files.each do |path|
236
+ variant = path == selected_path ? :secondary : :ghost
237
+ sidebar.child(Zaniah::UI::Button.new(File.basename(path), size: :sm, variant: variant).w_full
238
+ .on_click { on_select&.call(path) })
239
+ end
240
+ [Zaniah::Div.new.flex_row.bg(@theme.colors.background).child(sidebar).child(scroll), scroll]
241
+ end
242
+ end
243
+
244
+ def render_node(node, base_path: nil)
245
+ case node.type
246
+ when :header
247
+ size = {1 => :xl, 2 => :lg, 3 => :md}.fetch(node.attributes.fetch("level", 3).to_i, :sm)
248
+ Zaniah::UI::Label.new(node.text.to_s, size: size, wrap: :word)
249
+ when :codeblock
250
+ runs = Highlight.tokens(node).flat_map { |line| line.map { |token, text| {text: text, color: syntax_color(token)} } }
251
+ Zaniah::UI::Card.new(Zaniah::UI::Label.new(node.attributes["language"] || "code", tone: :muted, size: :xs),
252
+ Zaniah::UI::RichText.new(runs, selectable: false))
253
+ when :blockquote
254
+ Zaniah::Div.new.flex_row.gap(10).border(1).border_color(@theme.colors.accent).p(12)
255
+ .child(Zaniah::UI::Label.new(node.text.to_s, wrap: :word))
256
+ when :table
257
+ rows = node.text.to_s.lines.map { |line| line.chomp.split(" | ") }
258
+ columns = rows.first.to_a.each_index.map { |index| {key: index, label: "", width: 160, sortable: false, resizable: false} }
259
+ Zaniah::UI::Table.new(rows.map { |row| row.each_with_index.to_h }, columns: columns, height: [rows.length * 32 + 40, 120].max,
260
+ selection: :none)
261
+ when :hr
262
+ Zaniah::UI::Divider.new
263
+ when :img
264
+ path = node.attributes["src"].to_s
265
+ path = File.expand_path(path, base_path) if base_path && !Pathname.new(path).absolute?
266
+ File.file?(path) ? Zaniah::Image.new(path) : Zaniah::UI::EmptyState.new("Image unavailable", message: path)
267
+ else
268
+ Zaniah::UI::Label.new(node.text.to_s, wrap: :word)
269
+ end
270
+ rescue StandardError => error
271
+ Zaniah::UI::EmptyState.new("Preview error", message: error.message)
272
+ end
273
+
274
+ def syntax_color(token)
275
+ shortname = token.respond_to?(:shortname) ? token.shortname.to_s : ""
276
+ case shortname
277
+ when /k|c/ then @theme.colors.text_muted
278
+ when /nb|nc|nf|n/ then @theme.colors.accent
279
+ when /s|dl/ then @theme.colors.success
280
+ when /m|mi|mf/ then @theme.colors.warning
281
+ else @theme.colors.text
282
+ end
283
+ end
284
+ end
285
+
286
+ class Watcher
287
+ attr_reader :error
288
+
289
+ def initialize(path, latency: 0.1)
290
+ @path, @latency, @last, @event_at = path, latency, (File.mtime(path) rescue nil), 0.0
291
+ @watch = defined?(Zaniah::Platform) && Zaniah::Platform.watch(File.dirname(path), latency: latency)
292
+ end
293
+
294
+ def poll
295
+ unless File.file?(@path)
296
+ changed = @error.nil?
297
+ @error = Error.new("file not found: #{@path}")
298
+ return changed
299
+ end
300
+ if @error
301
+ @error = nil
302
+ return true
303
+ end
304
+ events = @watch ? @watch.poll(timeout: 0) : []
305
+ changed = events.any? { |event| File.expand_path(event.path) == File.expand_path(@path) }
306
+ changed ||= File.file?(@path) && File.mtime(@path) != @last
307
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
308
+ return false if changed && now - @event_at < @latency
309
+ @event_at = now if changed
310
+ @last = File.mtime(@path) if changed && File.file?(@path)
311
+ @error = nil if changed
312
+ changed
313
+ rescue StandardError => error
314
+ @error = error
315
+ false
316
+ end
317
+ end
318
+
319
+ class CLI
320
+ BACKENDS = %i[auto headless tui mac linux windows].freeze
321
+
322
+ def self.run(argv, out: $stdout, err: $stderr)
323
+ options = {export: nil, width: 900, height: 1000, theme: :dark, backend: :auto}
324
+ OptionParser.new do |opts|
325
+ opts.banner = "Usage: heze PATH [options]"
326
+ opts.on("--export PATH") { |v| options[:export] = v }
327
+ opts.on("--width N", Integer) { |v| options[:width] = v }
328
+ opts.on("--height N", Integer) { |v| options[:height] = v }
329
+ opts.on("--theme NAME") { |v| options[:theme] = v }
330
+ opts.on("--backend NAME") do |value|
331
+ backend = value.to_sym
332
+ raise Error, "unknown backend: #{value}" unless BACKENDS.include?(backend)
333
+ options[:backend] = backend
334
+ end
335
+ opts.on("--no-watch") { options[:no_watch] = true }
336
+ opts.on("--watch") { options[:watch] = true }
337
+ end.parse!(argv)
338
+ path = argv.fetch(0)
339
+ document_path = if File.directory?(path)
340
+ Directory.markdown(path).first or raise Error, "no Markdown files in #{path}"
341
+ elsif File.extname(path).downcase == ".svg"
342
+ path
343
+ else
344
+ path
345
+ end
346
+ document = File.extname(document_path).downcase == ".svg" ? SVG.parse(document_path) : Markdown.parse(Source.read(document_path))
347
+ if options[:export]
348
+ bytes = Renderer.new(theme: Theme.resolve(options[:theme]), width: options[:width], height: options[:height]).render(
349
+ document, base_path: File.dirname(File.expand_path(document_path))
350
+ )
351
+ File.binwrite(options[:export], bytes)
352
+ elsif out.tty? && options[:backend] != :headless
353
+ begin
354
+ target = File.directory?(path) ? Directory.markdown(path).first : path
355
+ files = File.directory?(path) ? Directory.markdown(path) : nil
356
+ watcher = options[:no_watch] || !target ? nil : Watcher.new(target)
357
+ loader = target && ->(selected = target) { Markdown.parse(Source.read(selected)) }
358
+ Renderer.new(theme: Theme.resolve(options[:theme]), width: options[:width], height: options[:height]).show(
359
+ document, backend: options[:backend], title: "Heze — #{File.basename(path)}", watcher: watcher, loader: loader,
360
+ files: files, selected_path: target
361
+ )
362
+ rescue StandardError => error
363
+ err.puts "heze: native preview unavailable: #{error.message}"
364
+ out.puts "heze: #{path} (#{document.type if document.respond_to?(:type)})"
365
+ end
366
+ else
367
+ out.puts "heze: #{path} (#{document.type if document.respond_to?(:type)})"
368
+ Directory.markdown(path).each { |entry| out.puts " #{entry}" } if File.directory?(path)
369
+ end
370
+ watch = options[:watch] || (!options[:no_watch] && !options[:export] && out.tty?)
371
+ watch(path, options, out: out, err: err) if watch
372
+ 0
373
+ rescue OptionParser::ParseError, KeyError, Error => error
374
+ err.puts "heze: #{error.message}"
375
+ 1
376
+ end
377
+
378
+ def self.watch(path, options, out:, err:)
379
+ target = File.directory?(path) ? Directory.markdown(path).first : path
380
+ return unless target
381
+ watcher = Watcher.new(target)
382
+ loop do
383
+ sleep 0.1
384
+ next unless watcher.poll
385
+ begin
386
+ document = Markdown.parse(Source.read(target))
387
+ if options[:export]
388
+ png = Renderer.new(theme: Theme.resolve(options[:theme]), width: options[:width], height: options[:height]).render(
389
+ document, base_path: File.dirname(File.expand_path(target))
390
+ )
391
+ File.binwrite(options[:export], png)
392
+ end
393
+ out.puts "heze: updated #{target}"
394
+ rescue StandardError => error
395
+ err.puts "heze: #{error.message}"
396
+ end
397
+ end
398
+ rescue Interrupt
399
+ out.puts "heze: stopped"
400
+ end
401
+ private_class_method :watch
402
+ end
403
+ end
data/sig/heze.rbs ADDED
@@ -0,0 +1,17 @@
1
+ module Heze
2
+ VERSION: String
3
+ class Error < StandardError
4
+ end
5
+ module Source
6
+ def self.read: (String path) -> String
7
+ end
8
+ module Markdown
9
+ def self.parse: (String text) -> untyped
10
+ end
11
+ module SVG
12
+ def self.parse: (String path) -> untyped
13
+ end
14
+ class Renderer
15
+ def render: (untyped document, ?base_path: String?) -> String
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heze
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: alkaid
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: antares
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: fiddle
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: kramdown
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.4'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.4'
68
+ - !ruby/object:Gem::Dependency
69
+ name: kramdown-parser-gfm
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: kochab
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.2'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.2'
96
+ - !ruby/object:Gem::Dependency
97
+ name: menkar
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.1'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.1'
110
+ - !ruby/object:Gem::Dependency
111
+ name: zaniah
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 0.6.0
117
+ - - "<"
118
+ - !ruby/object:Gem::Version
119
+ version: '0.7'
120
+ type: :runtime
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: 0.6.0
127
+ - - "<"
128
+ - !ruby/object:Gem::Version
129
+ version: '0.7'
130
+ description: Render Markdown and static SVG previews with the Zaniah renderer.
131
+ email:
132
+ - t.yudai92@gmail.com
133
+ executables:
134
+ - heze
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - CHANGELOG.md
139
+ - LICENSE.txt
140
+ - README.md
141
+ - Rakefile
142
+ - exe/heze
143
+ - lib/heze.rb
144
+ - lib/heze/version.rb
145
+ - sig/heze.rbs
146
+ homepage: https://github.com/noxdea/heze
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ allowed_push_host: https://rubygems.org
151
+ source_code_uri: https://github.com/noxdea/heze
152
+ rubygems_mfa_required: 'true'
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: 3.2.0
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubygems_version: 4.0.16
168
+ specification_version: 4
169
+ summary: Native Markdown and SVG previewer
170
+ test_files: []