pangrid 0.3.1 → 0.5.1
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 +5 -5
- data/lib/pangrid/frontend/webrick.rb +24 -5
- data/lib/pangrid/plugin.rb +5 -3
- data/lib/pangrid/plugins/acrosslite.rb +4 -0
- data/lib/pangrid/plugins/csv.rb +4 -1
- data/lib/pangrid/plugins/excel.rb +3 -0
- data/lib/pangrid/plugins/exolve.rb +128 -0
- data/lib/pangrid/plugins/json.rb +78 -0
- data/lib/pangrid/plugins/png.rb +25 -0
- data/lib/pangrid/plugins/qxw.rb +3 -0
- data/lib/pangrid/plugins/reddit.rb +5 -0
- data/lib/pangrid/plugins/text.rb +3 -0
- data/lib/pangrid/version.rb +1 -1
- data/lib/pangrid/xw.rb +8 -0
- metadata +25 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0ec0d0d4bf728c0021f9db679a2c0b3208b7a5971888ef4de9490e8dc07037b1
|
4
|
+
data.tar.gz: eb305d1960b5876c0d31d562a0734cd61f9c7683f0bed5cf5af2c3fb2f8b2f99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a8cc63d397ce5047e9e85b0665f7436af2aff9f59a8dec8d8b9f6ed9abb83b333d0f8f348fc3d460c908dcb6bc6b11338cba2ed7ad8d7af8bb29680dc0ad901
|
7
|
+
data.tar.gz: d9b5dec6380b0bcd6429e7a61f085330c329f70e75518a7c4471e4c5abab2c7d570c3696ef4034028627b8bb329d3b0aa4a861257d11adeedcaa8d3594a76b0c
|
@@ -26,11 +26,30 @@ class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
|
26
26
|
rescue Exception => e
|
27
27
|
out = e.inspect
|
28
28
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
|
30
|
+
response.header['Access-Control-Allow-Origin'] = '*'
|
31
|
+
|
32
|
+
case request.path
|
33
|
+
when "/"
|
34
|
+
template = IO.read(TEMPLATE)
|
35
|
+
response.status = 200
|
36
|
+
response.content_type = "text/html"
|
37
|
+
response.body = template % out
|
38
|
+
when "/blob"
|
39
|
+
response.status = 200
|
40
|
+
response.content_type = "application/octet-stream"
|
41
|
+
response.body = out
|
42
|
+
when "/json"
|
43
|
+
if request.query["to"] == "json"
|
44
|
+
response.status = 200
|
45
|
+
response.content_type = "text/json"
|
46
|
+
response.body = out
|
47
|
+
else
|
48
|
+
response.status = 200
|
49
|
+
response.content_type = "text/json"
|
50
|
+
response.body = '{ "error" : "non-json format requested" }'
|
51
|
+
end
|
52
|
+
end
|
34
53
|
end
|
35
54
|
end
|
36
55
|
|
data/lib/pangrid/plugin.rb
CHANGED
@@ -35,6 +35,8 @@ class Plugin
|
|
35
35
|
FAILED = []
|
36
36
|
MISSING_DEPS = {}
|
37
37
|
|
38
|
+
DESCRIPTION = ""
|
39
|
+
|
38
40
|
def self.inherited(subclass)
|
39
41
|
name = class_to_name(subclass.name)
|
40
42
|
#puts "Registered #{subclass} as #{name}"
|
@@ -62,13 +64,13 @@ class Plugin
|
|
62
64
|
|
63
65
|
def self.list_all
|
64
66
|
puts "-------------------------------------------------------"
|
65
|
-
puts "Available plugins:"
|
67
|
+
puts "Available plugins (F = from, T = to):"
|
66
68
|
puts "-------------------------------------------------------"
|
67
69
|
REGISTRY.keys.sort.each do |name|
|
68
70
|
plugin = REGISTRY[name]
|
69
71
|
provides = [:read, :write].select {|m| plugin.method_defined? m}
|
70
|
-
provides = provides.map {|m| {read: '
|
71
|
-
puts "
|
72
|
+
provides = provides.map {|m| {read: 'F', write: 'T'}[m]}.join
|
73
|
+
puts " #{name} [#{provides}]".ljust(30) + plugin.const_get(:DESCRIPTION)
|
72
74
|
end
|
73
75
|
if !MISSING_DEPS.empty?
|
74
76
|
puts
|
@@ -75,6 +75,8 @@ class AcrossLiteBinary < Plugin
|
|
75
75
|
# crossword, checksums
|
76
76
|
attr_accessor :xw, :cs
|
77
77
|
|
78
|
+
DESCRIPTION = "AcrossLite binary format (.puz)"
|
79
|
+
|
78
80
|
HEADER_FORMAT = "v A12 v V2 A4 v2 A12 c2 v3"
|
79
81
|
HEADER_CHECKSUM_FORMAT = "c2 v3"
|
80
82
|
EXT_HEADER_FORMAT = "A4 v2"
|
@@ -359,6 +361,8 @@ class AcrossLiteText < Plugin
|
|
359
361
|
|
360
362
|
attr_accessor :xw, :rebus
|
361
363
|
|
364
|
+
DESCRIPTION = "AcrossLite text format"
|
365
|
+
|
362
366
|
def initialize
|
363
367
|
@xw = XWord.new
|
364
368
|
end
|
data/lib/pangrid/plugins/csv.rb
CHANGED
@@ -26,6 +26,9 @@ require 'csv'
|
|
26
26
|
module Pangrid
|
27
27
|
|
28
28
|
class CSV < Plugin
|
29
|
+
|
30
|
+
DESCRIPTION = "CSV reader (see source comments for format)"
|
31
|
+
|
29
32
|
def read(data)
|
30
33
|
s = ::CSV.parse(data)
|
31
34
|
s.reject! {|row| row.compact.empty?}
|
@@ -37,7 +40,7 @@ class CSV < Plugin
|
|
37
40
|
h = s.shift.map {|c| c.split(/:\s*/)}
|
38
41
|
header = OpenStruct.new
|
39
42
|
h.each do |k, v|
|
40
|
-
header[k.downcase] = v
|
43
|
+
header[k.downcase.strip] = v
|
41
44
|
end
|
42
45
|
|
43
46
|
header.clues = header.clues.to_i
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# Exolve (https://github.com/viresh-ratnakar/exolve) is a browser-based
|
2
|
+
# crossword solver that allows you to embed the crossword and solving software
|
3
|
+
# in a single HTML file.
|
4
|
+
|
5
|
+
module Pangrid
|
6
|
+
|
7
|
+
module ExolveWriter
|
8
|
+
def write(xw)
|
9
|
+
headers = format_headers(xw)
|
10
|
+
across, down = xw.number
|
11
|
+
grid = format_grid(xw)
|
12
|
+
ac = format_clues(across, xw.across_clues)
|
13
|
+
dn = format_clues(down, xw.down_clues)
|
14
|
+
across = ["exolve-across:"] + indent(ac)
|
15
|
+
down = ["exolve-down:"] + indent(dn)
|
16
|
+
grid = ["exolve-grid:"] + indent(grid)
|
17
|
+
body = headers + grid + across + down
|
18
|
+
out = ["exolve-begin"] + indent(body) + ["exolve-end"]
|
19
|
+
out.join("\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
def format_headers(xw)
|
23
|
+
headers = [
|
24
|
+
'id', 'replace-with-unique-id',
|
25
|
+
'title', xw.title,
|
26
|
+
'setter', xw.author,
|
27
|
+
'width', xw.width,
|
28
|
+
'height', xw.height,
|
29
|
+
'copyright', xw.copyright,
|
30
|
+
'prelude', xw.preamble
|
31
|
+
]
|
32
|
+
headers.each_slice(2).select {|k, v| v}.map {|k, v| "exolve-#{k}: #{v}"}
|
33
|
+
end
|
34
|
+
|
35
|
+
def format_clues(numbers, clues)
|
36
|
+
numbers.zip(clues).map {|n, c| "#{n.to_s.rjust(2)} #{c}"}
|
37
|
+
end
|
38
|
+
|
39
|
+
def indent(lines)
|
40
|
+
lines.map {|x| " " + x}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ExolveReader
|
45
|
+
def read(data)
|
46
|
+
s = data.each_line.map(&:rstrip)
|
47
|
+
first = s.index('exolve-begin')
|
48
|
+
check("exolve-begin missing") { first }
|
49
|
+
last = s.index('exolve-end')
|
50
|
+
check("exolve-end missing") { last }
|
51
|
+
lines = s[(first + 1)...last]
|
52
|
+
xw = XWord.new
|
53
|
+
s = sections(lines)
|
54
|
+
s.each do |_, field, data|
|
55
|
+
if %w(title setter copyright prelude).include? field
|
56
|
+
xw[field] = data
|
57
|
+
elsif %w(height width).include? field
|
58
|
+
xw[field] = data.to_i
|
59
|
+
elsif %(across down).include? field
|
60
|
+
xw["#{field}_clues"] = data
|
61
|
+
elsif field == "grid"
|
62
|
+
xw.solution = parse_grid(data)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
xw
|
66
|
+
end
|
67
|
+
|
68
|
+
def sections(lines)
|
69
|
+
headers = lines.each.with_index.map do |l, i|
|
70
|
+
m = l.match(/^(\s+)exolve-(\w+):(.*)$/)
|
71
|
+
if m
|
72
|
+
_, indent, field, data = m.to_a
|
73
|
+
[i, field, data.strip]
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
headers.compact!
|
79
|
+
headers.push([lines.length, "", ""])
|
80
|
+
headers.each_cons(2) do |i, j|
|
81
|
+
if i[2].empty?
|
82
|
+
i[2] = lines[(i[0] + 1)...j[0]].map(&:strip)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
headers.pop
|
86
|
+
headers
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_grid_char(char)
|
90
|
+
case char
|
91
|
+
when '0'; :null
|
92
|
+
when '.'; :black
|
93
|
+
else; char
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse_grid(lines)
|
98
|
+
grid = lines.map(&:strip).map {|x| x.split(//)}
|
99
|
+
grid.map do |col|
|
100
|
+
col.map do |c|
|
101
|
+
Cell.new(:solution => parse_grid_char(c))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class ExolveFilled < Plugin
|
108
|
+
include ExolveReader
|
109
|
+
include ExolveWriter
|
110
|
+
|
111
|
+
DESCRIPTION = "Exolve writer with solutions"
|
112
|
+
|
113
|
+
def format_grid(xw)
|
114
|
+
xw.to_array(:black => '.', :null => '0').map(&:join)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class ExolveBlank < Plugin
|
119
|
+
include ExolveWriter
|
120
|
+
|
121
|
+
DESCRIPTION = "Exolve writer without solutions"
|
122
|
+
|
123
|
+
def format_grid(xw)
|
124
|
+
xw.to_array(:black => '.', :null => '0') {|c| 0}.map(&:join)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end # module Pangrid
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Json
|
2
|
+
#
|
3
|
+
# cell = { x : int, y : int, contents : string }
|
4
|
+
#
|
5
|
+
# xword = { rows : int,
|
6
|
+
# cols : int,
|
7
|
+
# cells : [cell],
|
8
|
+
# across : [string]
|
9
|
+
# down : [string]
|
10
|
+
# }
|
11
|
+
|
12
|
+
require 'json'
|
13
|
+
|
14
|
+
module Pangrid
|
15
|
+
|
16
|
+
class Json < Plugin
|
17
|
+
|
18
|
+
DESCRIPTION = "Simple JSON format"
|
19
|
+
|
20
|
+
def write(xw)
|
21
|
+
cells = []
|
22
|
+
(0 ... xw.height).each do |y|
|
23
|
+
(0 ... xw.width).each do |x|
|
24
|
+
cell = xw.solution[y][x]
|
25
|
+
s = case cell.solution
|
26
|
+
when :black; '#'
|
27
|
+
when :null; ''
|
28
|
+
when Rebus; cell.solution.inspect
|
29
|
+
else; cell.solution
|
30
|
+
end
|
31
|
+
|
32
|
+
cells.push({ x: x, y: y, contents: s })
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
h = {
|
37
|
+
rows: xw.height,
|
38
|
+
cols: xw.width,
|
39
|
+
cells: cells,
|
40
|
+
across: xw.across_clues,
|
41
|
+
down: xw.down_clues
|
42
|
+
}
|
43
|
+
|
44
|
+
::JSON.generate(h)
|
45
|
+
end
|
46
|
+
|
47
|
+
def read(data)
|
48
|
+
json = ::JSON.parse(data)
|
49
|
+
xw = XWord.new
|
50
|
+
|
51
|
+
xw.height = json['rows']
|
52
|
+
xw.width = json['cols']
|
53
|
+
xw.solution = Array.new(xw.height) { Array.new(xw.width) }
|
54
|
+
json['cells'].each do |c|
|
55
|
+
cell = Cell.new
|
56
|
+
s = c['contents']
|
57
|
+
cell.solution =
|
58
|
+
case s
|
59
|
+
when ""; :null
|
60
|
+
when "#"; :black
|
61
|
+
else
|
62
|
+
if s.length == 1
|
63
|
+
s
|
64
|
+
else
|
65
|
+
Rebus.new s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
x, y = c['x'], c['y']
|
69
|
+
xw.solution[y][x] = cell
|
70
|
+
end
|
71
|
+
xw.across_clues = json['across']
|
72
|
+
xw.down_clues = json['down']
|
73
|
+
xw
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
|
3
|
+
module Pangrid
|
4
|
+
|
5
|
+
class PNGThumbnail < Plugin
|
6
|
+
|
7
|
+
def write(xw)
|
8
|
+
black = ChunkyPNG::Color::BLACK
|
9
|
+
white = ChunkyPNG::Color::WHITE
|
10
|
+
red = ChunkyPNG::Color::rgb(255, 0, 0)
|
11
|
+
grey = ChunkyPNG::Color::rgb(128, 128, 128)
|
12
|
+
scale = 4
|
13
|
+
png = ChunkyPNG::Image.new(64, 64, ChunkyPNG::Color::TRANSPARENT)
|
14
|
+
xw.each_cell_with_coords do |x, y, cell|
|
15
|
+
c = cell.black? ? black : white
|
16
|
+
png.rect(
|
17
|
+
x * scale, y * scale, (x + 1) * scale, (y + 1) * scale,
|
18
|
+
stroke_color = grey, fill_color = c)
|
19
|
+
end
|
20
|
+
png.rect(0, 0, xw.width * scale, xw.height * scale, stroke_color = red)
|
21
|
+
png.to_blob
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # module Pangrid
|
data/lib/pangrid/plugins/qxw.rb
CHANGED
@@ -34,6 +34,8 @@ end
|
|
34
34
|
class RedditFilled < Plugin
|
35
35
|
include RedditWriter
|
36
36
|
|
37
|
+
DESCRIPTION = "Reddit format (with filled squares)"
|
38
|
+
|
37
39
|
def write(xw)
|
38
40
|
write_xw(xw)
|
39
41
|
end
|
@@ -48,6 +50,8 @@ end
|
|
48
50
|
class RedditBlank < Plugin
|
49
51
|
include RedditWriter
|
50
52
|
|
53
|
+
DESCRIPTION = "Reddit format (with blank squares)"
|
54
|
+
|
51
55
|
def write(xw)
|
52
56
|
write_xw(xw)
|
53
57
|
end
|
@@ -88,6 +92,7 @@ class RedditBlank < Plugin
|
|
88
92
|
#
|
89
93
|
# we have to be careful about leading/trailing |s
|
90
94
|
ix = lines.find_index {|row| row =~ /^[|\s-]+$/}
|
95
|
+
check("Could not find grid") { not ix.nil? }
|
91
96
|
width = lines[ix].gsub(/\s/, '').split('|').reject(&:empty?).length
|
92
97
|
lines = [lines[(ix - 1)]] + lines[(ix + 1) .. -1]
|
93
98
|
grid = lines.take_while {|i| is_grid_row(i)}
|
data/lib/pangrid/plugins/text.rb
CHANGED
data/lib/pangrid/version.rb
CHANGED
data/lib/pangrid/xw.rb
CHANGED
@@ -139,6 +139,14 @@ class XWord < OpenStruct
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
+
def each_cell_with_coords
|
143
|
+
(0 ... height).each do |y|
|
144
|
+
(0 ... width).each do |x|
|
145
|
+
yield [x, y, solution[y][x]]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
142
150
|
# {:black => char, :null => char} -> Any[][]
|
143
151
|
def to_array(opts = {})
|
144
152
|
opts = {:black => '#', :null => ' '}.merge(opts)
|
metadata
CHANGED
@@ -1,16 +1,30 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pangrid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin DeMello
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
|
11
|
+
date: 2022-02-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: webrick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.7.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.7.0
|
27
|
+
description:
|
14
28
|
email: martindemello@gmail.com
|
15
29
|
executables:
|
16
30
|
- pangrid
|
@@ -29,6 +43,9 @@ files:
|
|
29
43
|
- lib/pangrid/plugins/acrosslite.rb
|
30
44
|
- lib/pangrid/plugins/csv.rb
|
31
45
|
- lib/pangrid/plugins/excel.rb
|
46
|
+
- lib/pangrid/plugins/exolve.rb
|
47
|
+
- lib/pangrid/plugins/json.rb
|
48
|
+
- lib/pangrid/plugins/png.rb
|
32
49
|
- lib/pangrid/plugins/qxw.rb
|
33
50
|
- lib/pangrid/plugins/reddit.rb
|
34
51
|
- lib/pangrid/plugins/text.rb
|
@@ -39,7 +56,7 @@ homepage: https://github.com/martindemello/pangrid
|
|
39
56
|
licenses:
|
40
57
|
- MIT
|
41
58
|
metadata: {}
|
42
|
-
post_install_message:
|
59
|
+
post_install_message:
|
43
60
|
rdoc_options:
|
44
61
|
- "--main"
|
45
62
|
- README
|
@@ -56,9 +73,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
73
|
- !ruby/object:Gem::Version
|
57
74
|
version: '0'
|
58
75
|
requirements: []
|
59
|
-
|
60
|
-
|
61
|
-
signing_key:
|
76
|
+
rubygems_version: 3.2.22
|
77
|
+
signing_key:
|
62
78
|
specification_version: 4
|
63
79
|
summary: A crossword file format converter
|
64
80
|
test_files: []
|