pangrid 0.3.1 → 0.5.1

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: a5a1bfbede1e22eda584f80088055a2da23e973a
4
- data.tar.gz: 287b4baf8fe41be20437f6c16ad9fd4eca3dbf64
2
+ SHA256:
3
+ metadata.gz: 0ec0d0d4bf728c0021f9db679a2c0b3208b7a5971888ef4de9490e8dc07037b1
4
+ data.tar.gz: eb305d1960b5876c0d31d562a0734cd61f9c7683f0bed5cf5af2c3fb2f8b2f99
5
5
  SHA512:
6
- metadata.gz: 987f63c04f51c1575d2d47a82f9fdcafd2645656e5a6facc5ea77e8e02fff8216ac2d591c52e23909e00a5c149a67bb80937c936b2b7b8d8328a34f3d4751446
7
- data.tar.gz: 721b3f3ea706c965ca1a2fbd04d098a55c88aa97478dc2773c7faf03c46bc4461454000577117c7d3d53b8862ebc6ea9f706800be176731e7c33a0c9f4a1402c
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
- 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?}
@@ -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
@@ -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
@@ -5,6 +5,9 @@ module Pangrid
5
5
  QXW_GRID_ERROR = "Could not read grid from .qxw file"
6
6
 
7
7
  class Qxw < Plugin
8
+
9
+ DESCRIPTION = "QXW grid reader (rectangular grids only)"
10
+
8
11
  def read(data)
9
12
  xw = XWord.new
10
13
  lines = data.lines.map(&:chomp)
@@ -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 => '.')
@@ -1,3 +1,3 @@
1
1
  module Pangrid
2
- VERSION='0.3.1'
2
+ VERSION='0.5.1'
3
3
  end
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.3.1
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: 2016-03-19 00:00:00.000000000 Z
12
- dependencies: []
13
- description:
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
- rubyforge_project:
60
- rubygems_version: 2.5.1
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: []