pangrid 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ca64b939037c4c658be062373bc66156605c3833
4
- data.tar.gz: 8792223200b3f4a5bfdcd212127c6724b4a087ae
2
+ SHA256:
3
+ metadata.gz: 33b68bb3ffccf5e357c16637245e4488b3541070737f171e2773e035ccdfbcfd
4
+ data.tar.gz: 05b0853605cccef29232acb6f96e1a53e1356e830e85a7c158595c426c179fbf
5
5
  SHA512:
6
- metadata.gz: a3361137ff447f19ae7f2db13ca66c259b10a1f13387382881c25d5f63e5b84a7516999bf19c27a4b984ee08553f8461e7db66389e8391fe2f867b8523bd3aa5
7
- data.tar.gz: becb38b39f34b7e25876e5d1a7d148bb92a205fd59230fbb11ea465680099bf65bc46952391df8dafb8a6cf8f9aba7d073388d20504a92c2224058e0ccc689e7
6
+ metadata.gz: '00778b056954ae735461bc4782e5c9aae027ac6d0b2928a6253507afa8113abbc26ee908ba4e68b49c42d128a15b38406604eb6303e6214af88969824c2854b9'
7
+ data.tar.gz: de0cd413482c54b815ecf2eb97b41511b1a309858d61a2e3571cfff98fb1d6478fda2d5f4c332142253ae25969fa682dca97ddcb8d68d49f39b162f248614630
@@ -26,11 +26,30 @@ class Servlet < WEBrick::HTTPServlet::AbstractServlet
26
26
  rescue Exception => e
27
27
  out = e.inspect
28
28
  end
29
-
30
- template = IO.read(TEMPLATE)
31
- response.status = 200
32
- response.content_type = "text/html"
33
- response.body = template % out
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
 
@@ -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: 'from', write: 'to'}[m]}
71
- puts " " + name + " [" + provides.join(", ") + "]"
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
@@ -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?}
@@ -11,6 +11,9 @@ module Pangrid
11
11
  require_for_plugin 'excel', ['axlsx']
12
12
 
13
13
  class ExcelXSLX < Plugin
14
+
15
+ DESCRIPTION = "Excel writer. Useful to upload to google sheets."
16
+
14
17
  # styles
15
18
  STYLES = {
16
19
  :black => {:bg_color => "00"},
@@ -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
@@ -0,0 +1,49 @@
1
+ # Qxw savefile for rectangular grids [http://www.quinapalus.com/qxw.html]
2
+
3
+ module Pangrid
4
+
5
+ QXW_GRID_ERROR = "Could not read grid from .qxw file"
6
+
7
+ class Qxw < Plugin
8
+
9
+ DESCRIPTION = "QXW grid reader (rectangular grids only)"
10
+
11
+ def read(data)
12
+ xw = XWord.new
13
+ lines = data.lines.map(&:chomp)
14
+ gp = lines.find {|x| x =~ /^GP( \d+){6}$/}
15
+ check("Could not read grid size from .qxw file") { gp }
16
+ type, w, h, _ = gp.scan(/\d+/).map(&:to_i)
17
+ check("Only rectangular grids are supported") { type == 0 }
18
+ xw.width = w
19
+ xw.height = h
20
+ xw.solution = []
21
+ grid = lines.select {|x| x =~ /^SQ /}
22
+ grid.each do |line|
23
+ parts = line.scan(/\w+/)
24
+ check(QXW_GRID_ERROR) { parts.length == 6 || parts.length == 7 }
25
+ col, row, b, c = parts[1].to_i, parts[2].to_i, parts[5].to_i, parts[6]
26
+ cell = Cell.new
27
+ if b == 1
28
+ cell.solution = :black
29
+ elsif c == nil
30
+ cell.solution = :null
31
+ else
32
+ cell.solution = c
33
+ end
34
+ xw.solution[row] ||= []
35
+ xw.solution[row][col] = cell
36
+ end
37
+ check(QXW_GRID_ERROR) { xw.solution.length == xw.height }
38
+ check(QXW_GRID_ERROR) { xw.solution.all? {|i| i.compact.length == xw.width} }
39
+
40
+ # Placeholder clues
41
+ _, _, words_a, words_d = xw.number(true)
42
+ xw.across_clues = words_a.map {|i| "[#{i}]" }
43
+ xw.down_clues = words_d.map {|i| "[#{i}]" }
44
+
45
+ xw
46
+ end
47
+ end
48
+
49
+ end # module Pangrid
@@ -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)}
@@ -9,6 +9,9 @@
9
9
  module Pangrid
10
10
 
11
11
  class Text < Plugin
12
+
13
+ DESCRIPTION = "Basic text format (mostly for debugging)"
14
+
12
15
  def write(xw)
13
16
  across, down = xw.number
14
17
  rows = xw.to_array(:black => '#', :null => '.')
@@ -27,7 +30,7 @@ class Text < Plugin
27
30
  end
28
31
 
29
32
  def format_clues(numbers, clues, indent)
30
- numbers.zip(clues).map {|n, c| " "*indent + "#{n}. #{c}"}.join("\n")
33
+ numbers.zip(clues).map {|n, c| " "*indent + "#{n.to_s.rjust(2)}. #{c}"}.join("\n")
31
34
  end
32
35
  end
33
36
 
@@ -1,3 +1,3 @@
1
1
  module Pangrid
2
- VERSION='0.3.0'
2
+ VERSION='0.5.0'
3
3
  end
data/lib/pangrid/xw.rb CHANGED
@@ -53,6 +53,15 @@ class Cell
53
53
  rebus? ? solution.to_char : solution
54
54
  end
55
55
 
56
+ def to_w
57
+ case solution
58
+ when :black; '#'
59
+ when :null; '.'
60
+ when Rebus; solution.inspect
61
+ else; solution
62
+ end
63
+ end
64
+
56
65
  def inspect
57
66
  case solution
58
67
  when :black; '#'
@@ -86,19 +95,40 @@ class XWord < OpenStruct
86
95
  boundary?(x, y - 1) && !boundary?(x, y) && !boundary?(x, y + 1)
87
96
  end
88
97
 
89
- def number
98
+ # Word starting from a square
99
+ def word_from(x, y, dir)
100
+ s = ""
101
+ while !boundary?(x, y) do
102
+ s += solution[y][x].to_w
103
+ if dir == :across
104
+ x += 1
105
+ else
106
+ y += 1
107
+ end
108
+ end
109
+ s
110
+ end
111
+
112
+ def number(words = false)
90
113
  n, across, down = 1, [], []
114
+ words_a, words_d = [], []
91
115
  (0 ... height).each do |y|
92
116
  (0 ... width).each do |x|
93
- across << n if across? x, y
94
- down << n if down? x, y
117
+ if across? x, y
118
+ across << n
119
+ words_a << word_from(x, y, :across)
120
+ end
121
+ if down? x, y
122
+ down << n
123
+ words_d << word_from(x, y, :down)
124
+ end
95
125
  if across.last == n || down.last == n
96
126
  solution[y][x].number = n
97
127
  n += 1
98
128
  end
99
129
  end
100
130
  end
101
- [across, down]
131
+ words ? [across, down, words_a, words_d] : [across, down]
102
132
  end
103
133
 
104
134
  def each_cell
@@ -109,6 +139,14 @@ class XWord < OpenStruct
109
139
  end
110
140
  end
111
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
+
112
150
  # {:black => char, :null => char} -> Any[][]
113
151
  def to_array(opts = {})
114
152
  opts = {:black => '#', :null => ' '}.merge(opts)
@@ -137,7 +175,7 @@ class XWord < OpenStruct
137
175
  if c.rebus?
138
176
  r = c.solution
139
177
  if self.rebus[s]
140
- sym, char = self.rebus[s]
178
+ sym, _ = self.rebus[s]
141
179
  r.symbol = sym.to_s
142
180
  else
143
181
  k += 1
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.3.0
4
+ version: 0.5.0
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: 2016-03-11 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
11
+ date: 2021-10-04 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,10 @@ 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
49
+ - lib/pangrid/plugins/qxw.rb
32
50
  - lib/pangrid/plugins/reddit.rb
33
51
  - lib/pangrid/plugins/text.rb
34
52
  - lib/pangrid/utils.rb
@@ -38,7 +56,7 @@ homepage: https://github.com/martindemello/pangrid
38
56
  licenses:
39
57
  - MIT
40
58
  metadata: {}
41
- post_install_message:
59
+ post_install_message:
42
60
  rdoc_options:
43
61
  - "--main"
44
62
  - README
@@ -55,9 +73,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
73
  - !ruby/object:Gem::Version
56
74
  version: '0'
57
75
  requirements: []
58
- rubyforge_project:
59
- rubygems_version: 2.5.1
60
- signing_key:
76
+ rubygems_version: 3.2.22
77
+ signing_key:
61
78
  specification_version: 4
62
79
  summary: A crossword file format converter
63
80
  test_files: []