stacking-order 1.0.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +92 -0
- data/exe/stacking-order +72 -0
- data/lib/stacking_order/version.rb +5 -0
- data/lib/stacking_order.rb +82 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 13c66a0c61b2744526f1e6113775629730d07cda8d528630c103f5ae276bce4f
|
|
4
|
+
data.tar.gz: b8133c884d749092af5cb11e4b8b6879127cefb6a1f557fb95ba2eac888d738c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0eb9c0442393fea05b44c548f4ce5f249cdc64e0e13843cfc8200ba15e0f08797eda11f3ed9856b00cc521d36ef0d815d7f66cd6bcb7fd562f0c58c373afc4d5
|
|
7
|
+
data.tar.gz: 0d2a23b921e1341f76e0373844150e3fa633d83f186cbb3e6b2ee8817150d98898933ec7cd492ea621831e83f7007203d7bbe2b1caf6198a4742b54809534628
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jeremy
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Stacking Order
|
|
2
|
+
|
|
3
|
+
A tiny Ruby gem that computes how to arrange records on a paginated grid so that,
|
|
4
|
+
after cutting the printed stack into columns and re-stacking them, the final pile
|
|
5
|
+
remains in sequential order. It is useful for printing badges, tickets, or any
|
|
6
|
+
other grid-based layout that is cut in bulk.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bundle add stacking-order
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or add this line to your `Gemfile`:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem 'stacking-order'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Library
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require 'stacking_order'
|
|
26
|
+
|
|
27
|
+
order = StackingOrder.order(entries: 13, rows: 2, columns: 2)
|
|
28
|
+
# => [1, 5, 9, 13, 2, 6, 10, nil, 3, 7, 11, nil, 4, 8, 12]
|
|
29
|
+
# nil entries mark empty slots on the final, partially filled page.
|
|
30
|
+
|
|
31
|
+
StackingOrder.visualize(entries: 6, rows: 2, columns: 2)
|
|
32
|
+
# Prints page-by-page grids plus the stack order after cutting.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### CLI
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
$ stacking-order --entries 15 --rows 2 --columns 2
|
|
39
|
+
|
|
40
|
+
Stacking order (15 positions):
|
|
41
|
+
[1, 5, 9, 13, 2, 6, 10, nil, 3, 7, 11, nil, 4, 8, 12]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
$ stacking-order --entries 6 --rows 2 --columns 2 --visualize
|
|
45
|
+
|
|
46
|
+
============================================================
|
|
47
|
+
Visualizing stacking order for 13 entries, 2 row(s), 2 column(s)
|
|
48
|
+
============================================================
|
|
49
|
+
Result: [1, 5, 9, 13, 2, 6, 10, nil, 3, 7, 11, nil, 4, 8, 12]
|
|
50
|
+
|
|
51
|
+
Pages layout:
|
|
52
|
+
|
|
53
|
+
Page 1:
|
|
54
|
+
Row 1: 1 | 5
|
|
55
|
+
Row 2: 9 | 13
|
|
56
|
+
|
|
57
|
+
Page 2:
|
|
58
|
+
Row 1: 2 | 6
|
|
59
|
+
Row 2: 10 | nil
|
|
60
|
+
|
|
61
|
+
Page 3:
|
|
62
|
+
Row 1: 3 | 7
|
|
63
|
+
Row 2: 11 | nil
|
|
64
|
+
|
|
65
|
+
Page 4:
|
|
66
|
+
Row 1: 4 | 8
|
|
67
|
+
Row 2: 12
|
|
68
|
+
|
|
69
|
+
After cutting and stacking:
|
|
70
|
+
Position [1,1] stack (bottom→top): 1, 2, 3, 4
|
|
71
|
+
Position [1,2] stack (bottom→top): 5, 6, 7, 8
|
|
72
|
+
Position [2,1] stack (bottom→top): 9, 10, 11, 12
|
|
73
|
+
Position [2,2] stack (bottom→top): 13
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The CLI prints the resulting sequence (or, with `--visualize`, renders the
|
|
77
|
+
page-by-page grids and stack order) and returns non-zero on invalid arguments.
|
|
78
|
+
|
|
79
|
+
### Real-world usage
|
|
80
|
+
|
|
81
|
+
This gem powers the stacking order calculations in
|
|
82
|
+
[pecha-printer](https://github.com/jerefrer/pecha-printer), which itself backs
|
|
83
|
+
the hosted service at <https://pecha-printer.frerejeremy.me>. By publishing the
|
|
84
|
+
logic as a gem, the same algorithm can be reused by other Ruby/Rails projects or
|
|
85
|
+
invoked manually via the CLI.
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
- Run tests: `bundle exec rake test`
|
|
90
|
+
- Build the gem: `bundle exec rake build`
|
|
91
|
+
|
|
92
|
+
Pull requests and issues are welcome.
|
data/exe/stacking-order
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'optparse'
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
require 'stacking_order'
|
|
8
|
+
rescue LoadError
|
|
9
|
+
lib_path = File.expand_path('../lib', __dir__)
|
|
10
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
|
11
|
+
require 'stacking_order'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
options = {}
|
|
15
|
+
parser = OptionParser.new do |opts|
|
|
16
|
+
opts.banner = 'Usage: stacking-order --entries N --rows R --columns C'
|
|
17
|
+
|
|
18
|
+
opts.on('-e', '--entries N', Integer, 'Total number of entries') do |value|
|
|
19
|
+
options[:entries] = value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
opts.on('-r', '--rows R', Integer, 'Number of rows per page') do |value|
|
|
23
|
+
options[:rows] = value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
opts.on('-c', '--columns C', Integer, 'Number of columns per page') do |value|
|
|
27
|
+
options[:columns] = value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
opts.on('--visualize', 'Print page-by-page layout and stack order') do
|
|
31
|
+
options[:visualize] = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts.on('-v', '--version', 'Print gem version') do
|
|
35
|
+
puts StackingOrder::VERSION
|
|
36
|
+
exit 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on('-h', '--help', 'Show this help message') do
|
|
40
|
+
puts opts
|
|
41
|
+
exit 0
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
parser.parse!(ARGV)
|
|
47
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
48
|
+
warn e.message
|
|
49
|
+
warn parser
|
|
50
|
+
exit 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
missing = %i[entries rows columns].reject { |key| options[key] }
|
|
54
|
+
if missing.any?
|
|
55
|
+
warn "Missing required options: #{missing.join(', ')}"
|
|
56
|
+
warn parser
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
result = StackingOrder.order(entries: options[:entries], rows: options[:rows], columns: options[:columns])
|
|
62
|
+
rescue ArgumentError => e
|
|
63
|
+
warn e.message
|
|
64
|
+
exit 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if options[:visualize]
|
|
68
|
+
StackingOrder.visualize(entries: options[:entries], rows: options[:rows], columns: options[:columns])
|
|
69
|
+
else
|
|
70
|
+
puts "Stacking order (#{result.length} positions):"
|
|
71
|
+
puts result.inspect
|
|
72
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'stacking_order/version'
|
|
4
|
+
|
|
5
|
+
# Calculates how to print entries in a grid so that stack-cut pages
|
|
6
|
+
# reassemble into sequential order.
|
|
7
|
+
module StackingOrder
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Public API for calculating the stacking order for printing entries on pages
|
|
11
|
+
# with a grid layout. See README for details.
|
|
12
|
+
#
|
|
13
|
+
# @param entries [Integer] Total number of entries to print
|
|
14
|
+
# @param rows [Integer] Number of rows in the grid on each page
|
|
15
|
+
# @param columns [Integer] Number of columns in the grid on each page
|
|
16
|
+
# @return [Array<Integer, nil>] The order in which to print entries (with nil for empty cells)
|
|
17
|
+
def order(entries:, rows:, columns:)
|
|
18
|
+
validate_arguments!(entries, rows, columns)
|
|
19
|
+
|
|
20
|
+
return [] if entries.zero?
|
|
21
|
+
|
|
22
|
+
cells_per_page = rows * columns
|
|
23
|
+
num_pages = (entries.to_f / cells_per_page).ceil
|
|
24
|
+
|
|
25
|
+
result = []
|
|
26
|
+
|
|
27
|
+
num_pages.times do |page_index|
|
|
28
|
+
cells_per_page.times do |cell_index|
|
|
29
|
+
entry_number = (cell_index * num_pages) + page_index + 1
|
|
30
|
+
result << (entry_number <= entries ? entry_number : nil)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
result.pop while result.last.nil?
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Utility method that prints the layout for the provided configuration,
|
|
39
|
+
# showing the entries on each page and the resulting stacks after cutting.
|
|
40
|
+
# Useful for debugging or CLI demos.
|
|
41
|
+
def visualize(entries:, rows:, columns:, io: $stdout)
|
|
42
|
+
result = order(entries: entries, rows: rows, columns: columns)
|
|
43
|
+
cells_per_page = rows * columns
|
|
44
|
+
num_pages = (entries.to_f / cells_per_page).ceil
|
|
45
|
+
|
|
46
|
+
io.puts
|
|
47
|
+
io.puts('=' * 60)
|
|
48
|
+
io.puts("Visualizing stacking order for #{entries} entries, #{rows} row(s), #{columns} column(s)")
|
|
49
|
+
io.puts('=' * 60)
|
|
50
|
+
io.puts("Result: #{result.inspect}")
|
|
51
|
+
io.puts("\nPages layout:")
|
|
52
|
+
|
|
53
|
+
result.each_slice(cells_per_page).with_index do |page, page_num|
|
|
54
|
+
io.puts("\nPage #{page_num + 1}:")
|
|
55
|
+
page.each_slice(columns).with_index do |row_values, row_num|
|
|
56
|
+
formatted = row_values.map { |value| value ? format('%3d', value) : 'nil' }.join(' | ')
|
|
57
|
+
io.puts(" Row #{row_num + 1}: #{formatted}")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
io.puts("\nAfter cutting and stacking:")
|
|
62
|
+
cells_per_page.times do |cell_idx|
|
|
63
|
+
row_idx = cell_idx / columns
|
|
64
|
+
col_idx = cell_idx % columns
|
|
65
|
+
stack = []
|
|
66
|
+
|
|
67
|
+
num_pages.times do |page_idx|
|
|
68
|
+
pos = (page_idx * cells_per_page) + cell_idx
|
|
69
|
+
stack << (pos < result.length ? result[pos] : nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
io.puts(" Position [#{row_idx + 1},#{col_idx + 1}] stack (bottom→top): #{stack.compact.join(', ')}")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_arguments!(entries, rows, columns)
|
|
77
|
+
raise ArgumentError, 'entries must be non-negative' if entries.negative?
|
|
78
|
+
raise ArgumentError, 'rows must be positive' if rows <= 0
|
|
79
|
+
raise ArgumentError, 'columns must be positive' if columns <= 0
|
|
80
|
+
end
|
|
81
|
+
private_class_method :validate_arguments!
|
|
82
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stacking-order
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jeremy
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2025-11-25 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: A tiny utility that reorders records for stack-cut badge printing, available
|
|
41
|
+
as both a Ruby library and a command-line tool.
|
|
42
|
+
email:
|
|
43
|
+
- jeremy@example.com
|
|
44
|
+
executables:
|
|
45
|
+
- stacking-order
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- LICENSE.txt
|
|
50
|
+
- README.md
|
|
51
|
+
- exe/stacking-order
|
|
52
|
+
- lib/stacking_order.rb
|
|
53
|
+
- lib/stacking_order/version.rb
|
|
54
|
+
homepage: https://github.com/jeremy/stacking-order
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
homepage_uri: https://github.com/jeremy/stacking-order
|
|
59
|
+
source_code_uri: https://github.com/jeremy/stacking-order
|
|
60
|
+
changelog_uri: https://github.com/jeremy/stacking-order
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.1'
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 3.6.2
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: Compute cut-and-stack printing orders for grid-based badges.
|
|
78
|
+
test_files: []
|