labrat 0.1.13
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/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +102 -0
- data/README.org +644 -0
- data/Rakefile +12 -0
- data/TODO.org +7 -0
- data/bin/console +15 -0
- data/bin/labrat +74 -0
- data/bin/labrat-install +85 -0
- data/bin/setup +8 -0
- data/img/sample.jpg +0 -0
- data/img/sample.pdf +82 -0
- data/img/sample.png +0 -0
- data/labrat.gemspec +40 -0
- data/lib/config_files/config.yml +121 -0
- data/lib/config_files/labeldb.yml +1010 -0
- data/lib/config_files/labeldb_usr.yml +24 -0
- data/lib/labrat/arg_parser.rb +481 -0
- data/lib/labrat/config.rb +188 -0
- data/lib/labrat/errors.rb +13 -0
- data/lib/labrat/hash.rb +38 -0
- data/lib/labrat/label.rb +158 -0
- data/lib/labrat/label_db.rb +49 -0
- data/lib/labrat/options.rb +184 -0
- data/lib/labrat/read_files.rb +40 -0
- data/lib/labrat/version.rb +5 -0
- data/lib/labrat.rb +20 -0
- data/lib/lisp/labrat.el +108 -0
- metadata +219 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labrat
|
4
|
+
class OptionError < StandardError; end
|
5
|
+
|
6
|
+
class DimensionError < StandardError; end
|
7
|
+
|
8
|
+
class LabelNameError < StandardError; end
|
9
|
+
|
10
|
+
class EmptyLabelError < StandardError; end
|
11
|
+
|
12
|
+
class RecursionError < StandardError; end
|
13
|
+
end
|
data/lib/labrat/hash.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Hash
|
3
|
+
# Transform hash keys to symbols suitable for calling as methods, i.e.,
|
4
|
+
# translate any hyphens to underscores. This is the form we want to keep
|
5
|
+
# config hashes in Labrat.
|
6
|
+
def methodize
|
7
|
+
transform_keys { |k| k.to_s.gsub('-', '_').to_sym }
|
8
|
+
end
|
9
|
+
|
10
|
+
# Convert the given Hash into a Array of Strings that represent an
|
11
|
+
# equivalent set of command-line args and pass them into the #parse method.
|
12
|
+
def optionize
|
13
|
+
options = []
|
14
|
+
each_pair do |k, v|
|
15
|
+
key = k.to_s.gsub('_', '-')
|
16
|
+
options <<
|
17
|
+
if [TrueClass, FalseClass].include?(v.class)
|
18
|
+
v ? "--#{key}" : "--no-#{key}"
|
19
|
+
else
|
20
|
+
"--#{key}=#{v}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
options
|
24
|
+
end
|
25
|
+
|
26
|
+
def report(title)
|
27
|
+
warn "#{title}:"
|
28
|
+
if empty?
|
29
|
+
warn " [[Empty]]"
|
30
|
+
else
|
31
|
+
each do |k, v|
|
32
|
+
val = v.class == Float ? v.round(2).to_s + 'pt' : v
|
33
|
+
warn " #{k}: #{val}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
warn ""
|
37
|
+
end
|
38
|
+
end
|
data/lib/labrat/label.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labrat
|
4
|
+
class Label
|
5
|
+
attr_reader :ops
|
6
|
+
attr_accessor :texts
|
7
|
+
|
8
|
+
def initialize(texts, ops)
|
9
|
+
@ops = ops
|
10
|
+
unless @ops.nl_sep.nil? || @ops.nl_sep == ''
|
11
|
+
@texts = texts.map { |t| t.gsub(ops.nl_sep, "\n") }
|
12
|
+
end
|
13
|
+
if @ops.copies > 1
|
14
|
+
duped_texts = []
|
15
|
+
@texts.each { |t| @ops.copies.times { duped_texts << t } }
|
16
|
+
@texts = duped_texts
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate
|
21
|
+
# The default margin is 0.5in on all sides, way too big for labels, so
|
22
|
+
# it is important to set these here. The margins' designation as "top,"
|
23
|
+
# "left," "bottom," and "right" take into account the page layout. That
|
24
|
+
# is, the left margin is on the left in both portrait and landscape
|
25
|
+
# orientations. But I want the user to be able to set the margins
|
26
|
+
# according to the label type, independent of the orientation. I adopt
|
27
|
+
# the convention that the margins are named assuming a portrait
|
28
|
+
# orientation and swap them here so that when Prawn swaps them again,
|
29
|
+
# they come out correct.
|
30
|
+
layout = ops.landscape ? :landscape : :portrait
|
31
|
+
if layout == :portrait
|
32
|
+
tpm = ops.top_page_margin
|
33
|
+
bpm = ops.bottom_page_margin
|
34
|
+
lpm = ops.left_page_margin
|
35
|
+
rpm = ops.right_page_margin
|
36
|
+
else
|
37
|
+
lpm = ops.top_page_margin
|
38
|
+
rpm = ops.bottom_page_margin
|
39
|
+
tpm = ops.left_page_margin
|
40
|
+
bpm = ops.right_page_margin
|
41
|
+
end
|
42
|
+
out_file = File.expand_path(ops.out_file)
|
43
|
+
Prawn::Document.generate(out_file, page_size: [ops.page_width, ops.page_height],
|
44
|
+
left_margin: lpm, right_margin: rpm,
|
45
|
+
top_margin: tpm, bottom_margin: bpm,
|
46
|
+
page_layout: layout) do |pdf|
|
47
|
+
# Define a grid with each grid box to be used for a single label.
|
48
|
+
pdf.define_grid(rows: ops.rows, columns: ops.columns,
|
49
|
+
row_gutter: ops.row_gap, column_gutter: ops.column_gap)
|
50
|
+
if ops.verbose
|
51
|
+
warn "Page dimensions:"
|
52
|
+
warn " [pg_wd, pg_ht] = [#{ops.page_width.round(2)}pt,#{ops.page_height.round(2)}pt]"
|
53
|
+
warn " orientation: #{layout}"
|
54
|
+
warn " [rows, columns] = [#{ops.rows},#{ops.columns}]"
|
55
|
+
warn " [lpm, rpm] = [#{lpm.round(2)}pt,#{rpm.round(2)}pt]"
|
56
|
+
warn " [tpm, bpm] = [#{tpm.round(2)}pt,#{bpm.round(2)}pt]"
|
57
|
+
warn ""
|
58
|
+
end
|
59
|
+
if ops.template
|
60
|
+
# Replace any texts with the numbers and show the grid.
|
61
|
+
self.texts = (1..(ops.rows * ops.columns)).map(&:to_s)
|
62
|
+
ops.font_name = 'Helvetica'
|
63
|
+
ops.font_style = 'bold'
|
64
|
+
ops.font_size = 16
|
65
|
+
pdf.grid.show_all
|
66
|
+
end
|
67
|
+
raise EmptyLabelError, "Empty label" if waste_of_labels?
|
68
|
+
|
69
|
+
last_k = texts.size - 1
|
70
|
+
lab_dims_reported = false
|
71
|
+
texts.each_with_index do |text, k|
|
72
|
+
row, col = row_col(k + 1)
|
73
|
+
pdf.grid(row, col).bounding_box do
|
74
|
+
bounds = pdf.bounds
|
75
|
+
pdf.stroke_bounds if ops.grid
|
76
|
+
box_wd = (bounds.right - bounds.left) - ops.left_pad - ops.right_pad
|
77
|
+
box_ht = (bounds.top - bounds.bottom) - ops.top_pad - ops.bottom_pad
|
78
|
+
box_x = ops.left_pad + ops.delta_x
|
79
|
+
box_y = ops.bottom_pad + box_ht + ops.delta_y
|
80
|
+
pdf.font ops.font_name, style: ops.font_style, size: ops.font_size.to_f
|
81
|
+
pdf.text_box(text, width: box_wd, height: box_ht,
|
82
|
+
align: ops.h_align, valign: ops.v_align,
|
83
|
+
overflow: :truncate, at: [box_x, box_y])
|
84
|
+
if ops.verbose && !lab_dims_reported
|
85
|
+
warn "Label text box dimensions:"
|
86
|
+
warn " [box_wd, box_ht] = [#{box_wd.round(2)}pt,#{box_ht.round(2)}pt]"
|
87
|
+
warn " [box_x, box_y] = [#{box_x.round(2)}pt,#{box_y.round(2)}pt]"
|
88
|
+
warn " [delta_x, delta_y] = [#{ops.delta_x.round(2)}pt,#{ops.delta_y.round(2)}pt]"
|
89
|
+
warn ''
|
90
|
+
lab_dims_reported = true
|
91
|
+
end
|
92
|
+
if ops.verbose
|
93
|
+
warn "Label \##{(k % lpp) + 1} on page #{page_num(k)} at row #{row + 1}, column #{col + 1}:"
|
94
|
+
warn '-------------------'
|
95
|
+
warn text
|
96
|
+
warn '-------------------'
|
97
|
+
warn ''
|
98
|
+
end
|
99
|
+
pdf.start_new_page if needs_new_page?(k, last_k)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def print
|
107
|
+
cmd = ops.print_command.gsub('%p', ops.printer).gsub('%o', ops.out_file)
|
108
|
+
if ops.verbose
|
109
|
+
warn "Printing with:"
|
110
|
+
warn " #{cmd} &"
|
111
|
+
end
|
112
|
+
system("#{cmd} &")
|
113
|
+
end
|
114
|
+
|
115
|
+
def view
|
116
|
+
cmd = ops.view_command.gsub('%o', ops.out_file)
|
117
|
+
if ops.verbose
|
118
|
+
warn "Viewing with:"
|
119
|
+
warn " #{cmd} &"
|
120
|
+
end
|
121
|
+
system("#{cmd} &")
|
122
|
+
end
|
123
|
+
|
124
|
+
def remove
|
125
|
+
FileUtils.rm(ops.out_file)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Labels per page
|
129
|
+
def lpp
|
130
|
+
ops.rows * ops.columns
|
131
|
+
end
|
132
|
+
|
133
|
+
# Page number of the kth label
|
134
|
+
def page_num(k)
|
135
|
+
k.divmod(lpp)[0] + 1
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return the 0-based row and column within a page on which the k-th
|
139
|
+
# (1-based) label should be printed.
|
140
|
+
def row_col(k)
|
141
|
+
k_on_page = (k + ops.start_label - 2) % lpp
|
142
|
+
k_on_page.divmod(ops.columns)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Should we emit a new page at this point?
|
146
|
+
def needs_new_page?(k, last_k)
|
147
|
+
return false if k == last_k
|
148
|
+
r, c = row_col(k + 1)
|
149
|
+
(r + 1 == ops.rows && c + 1 == ops.columns)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Would we just be printing blank labels?
|
153
|
+
def waste_of_labels?
|
154
|
+
(texts.nil? || texts.empty? || texts.all?(&:blank?)) &&
|
155
|
+
!ops.view
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labrat
|
4
|
+
module LabelDb
|
5
|
+
class << self
|
6
|
+
# Module-level variable to hold the merged database.
|
7
|
+
attr_accessor :db
|
8
|
+
end
|
9
|
+
|
10
|
+
# Read in the Labrat database of label settings, merging system and user
|
11
|
+
# databases.
|
12
|
+
def self.read(dir_prefix: '')
|
13
|
+
self.db = Config.read('labrat', base: 'labeldb', dir_prefix: dir_prefix)
|
14
|
+
.transform_keys(&:to_sym)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return a hash of config settings for the label named by labname.
|
18
|
+
def self.[](labname)
|
19
|
+
read unless db
|
20
|
+
db[labname.to_sym] || {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set a runtime configuration for a single labelname.
|
24
|
+
def self.[]=(labname, config = {})
|
25
|
+
read unless db
|
26
|
+
db[labname.to_sym] = config
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return an Array of label names.
|
30
|
+
def self.known_names
|
31
|
+
read unless db
|
32
|
+
db.keys.sort
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.db_paths(dir_prefix = '')
|
36
|
+
system_db_paths(dir_prefix) + user_db_paths(dir_prefix)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.system_db_paths(dir_prefix = '')
|
40
|
+
paths = Config.config_paths('labrat', base: 'labeldb', dir_prefix: dir_prefix)
|
41
|
+
paths[:system]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.user_db_paths(dir_prefix = '')
|
45
|
+
paths = Config.config_paths('labrat', base: 'labeldb', dir_prefix: dir_prefix)
|
46
|
+
paths[:user]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labrat
|
4
|
+
# The Options class is a glorified Hash, a container for the options
|
5
|
+
# settings gathered from the defaults, the config files, the command line,
|
6
|
+
# and perhaps environment. An Options instance can be handed off to the
|
7
|
+
# label-printing objects to inform its formatting, printing, etc.
|
8
|
+
class Options
|
9
|
+
attr_accessor :label, :page_width, :page_height,
|
10
|
+
:left_page_margin, :right_page_margin,
|
11
|
+
:top_page_margin, :bottom_page_margin,
|
12
|
+
:rows, :columns, :row_gap, :column_gap, :landscape,
|
13
|
+
:start_label, :grid,
|
14
|
+
:h_align, :v_align,
|
15
|
+
:left_pad, :right_pad, :top_pad, :bottom_pad,
|
16
|
+
:delta_x, :delta_y,
|
17
|
+
:font_name, :font_style, :font_size,
|
18
|
+
:in_file, :nl_sep, :label_sep, :copies,
|
19
|
+
:printer, :out_file, :print_command, :view_command, :view,
|
20
|
+
:template, :verbose, :msg
|
21
|
+
|
22
|
+
# Initialize with an optional hash of default values for the attributes.
|
23
|
+
def initialize(**init)
|
24
|
+
self.label = init[:label] || nil
|
25
|
+
# Per-page attributes
|
26
|
+
self.page_width = init[:page_width] || 24.mm
|
27
|
+
self.page_height = init[:page_height] || 87.mm
|
28
|
+
self.left_page_margin = init[:left_page_margin] || 5.mm
|
29
|
+
self.right_page_margin = init[:right_page_margin] || 5.mm
|
30
|
+
self.top_page_margin = init[:top_page_margin] || 0.mm
|
31
|
+
self.bottom_page_margin = init[:bottom_page_margin] || 0.mm
|
32
|
+
self.rows = init[:rows] || 1
|
33
|
+
self.columns = init[:columns] || 1
|
34
|
+
self.row_gap = init[:row_gap] || 0.mm
|
35
|
+
self.column_gap = init[:column_gap] || 0.mm
|
36
|
+
self.start_label = init[:start_label] || 1
|
37
|
+
self.landscape = init.fetch(:landscape, false)
|
38
|
+
# Per-label attributes
|
39
|
+
self.h_align = init[:h_align]&.to_sym || :center
|
40
|
+
self.v_align = init[:v_align]&.to_sym || :center
|
41
|
+
self.left_pad = init[:left_pad] || 4.5.mm
|
42
|
+
self.right_pad = init[:right_pad] || 4.5.mm
|
43
|
+
self.top_pad = init[:top_pad] || 0
|
44
|
+
self.bottom_pad = init[:bottom_pad] || 0
|
45
|
+
self.delta_x = init[:delta_x] || 0
|
46
|
+
self.delta_y = init[:delta_y] || 0
|
47
|
+
self.font_name = init[:font_name] || 'Helvetica'
|
48
|
+
self.font_style = init[:font_style]&.to_sym || :normal
|
49
|
+
self.font_size = init[:font_size] || 12
|
50
|
+
# Input attributes
|
51
|
+
self.in_file = init[:in_file] || nil
|
52
|
+
self.nl_sep = init[:nl_sep] || '++'
|
53
|
+
self.label_sep = init[:label_sep] || ']*['
|
54
|
+
self.copies = init[:copies] || 1
|
55
|
+
# Output attributes
|
56
|
+
self.printer = init[:printer] || ENV['LABRAT_PRINTER'] || ENV['PRINTER'] || 'dymo'
|
57
|
+
self.out_file = init[:out_file] || 'labrat.pdf'
|
58
|
+
self.print_command = init[:print_command] || 'lpr -P %p %o'
|
59
|
+
self.view_command = init[:view_command] || 'qpdfview --unique --instance labrat %o'
|
60
|
+
self.view = init.fetch(:view, false)
|
61
|
+
self.template = init.fetch(:landscape, false)
|
62
|
+
self.grid = init.fetch(:gid, false)
|
63
|
+
self.verbose = init.fetch(:verbose, false)
|
64
|
+
self.msg = init[:msg] || nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# High-level setting of options from config files, and given command-line
|
68
|
+
# args.
|
69
|
+
def self.set_options(args, verbose: false)
|
70
|
+
# Default, built-in config; set verbose to param.
|
71
|
+
default_config = Labrat::Options.new(verbose: verbose).to_hash
|
72
|
+
default_config.report("Default settings") if verbose
|
73
|
+
|
74
|
+
# Config files
|
75
|
+
file_config = Labrat::Config.read('labrat', verbose: verbose)
|
76
|
+
file_config.report("Settings from merged config files") if verbose
|
77
|
+
file_options = Labrat::ArgParser.new.parse(file_config, prior: default_config, verbose: verbose)
|
78
|
+
|
79
|
+
# Command-line
|
80
|
+
if verbose
|
81
|
+
warn "Command-line:"
|
82
|
+
args.each do |arg|
|
83
|
+
warn arg.to_s
|
84
|
+
end
|
85
|
+
warn ""
|
86
|
+
end
|
87
|
+
Labrat::ArgParser.new.parse(args, prior: file_options, verbose: verbose)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return any string in msg, e.g., the usage help or error.
|
91
|
+
def to_s
|
92
|
+
msg
|
93
|
+
end
|
94
|
+
|
95
|
+
# Allow hash-like assignment to attributes. This allows an Options object
|
96
|
+
# to be used, for example, in the OptionParser#parse :into parameter.
|
97
|
+
def []=(att, val)
|
98
|
+
att = att.to_s.gsub('-', '_')
|
99
|
+
send("#{att}=", val)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Allow hash-like access to attributes. This allows an Options object
|
103
|
+
# to be used, for example, in the OptionParser#parse :into parameter.
|
104
|
+
def [](att)
|
105
|
+
att = att.to_s.gsub('-', '_')
|
106
|
+
send(att.to_s)
|
107
|
+
end
|
108
|
+
|
109
|
+
# For testing, return an Array of the attributes as symbols.
|
110
|
+
def self.attrs
|
111
|
+
instance_methods(false).grep(/\A[a-z_]+=\Z/)
|
112
|
+
.map { |a| a.to_s.sub(/=\z/, '').to_sym }
|
113
|
+
end
|
114
|
+
|
115
|
+
# For testing, return an Array of the flags-form of the attributes, i.e.,
|
116
|
+
# with the underscores, _, replaced with hyphens.
|
117
|
+
def self.flags
|
118
|
+
attrs.map { |a| a.gsub('_', '-') }
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return a hash of the values in this Options object. This is the
|
122
|
+
# canonical form of a Hash for Labrat, i.e., symbolic keys with any
|
123
|
+
# hyphens translated into underscores. Don't include the msg attribute.
|
124
|
+
def to_hash
|
125
|
+
{
|
126
|
+
label: label,
|
127
|
+
page_width: page_width,
|
128
|
+
page_height: page_height,
|
129
|
+
left_page_margin: left_page_margin,
|
130
|
+
right_page_margin: right_page_margin,
|
131
|
+
top_page_margin: top_page_margin,
|
132
|
+
bottom_page_margin: bottom_page_margin,
|
133
|
+
rows: rows,
|
134
|
+
columns: columns,
|
135
|
+
row_gap: row_gap,
|
136
|
+
column_gap: column_gap,
|
137
|
+
grid: grid,
|
138
|
+
start_label: start_label,
|
139
|
+
landscape: landscape,
|
140
|
+
# Per-label attributes
|
141
|
+
h_align: h_align,
|
142
|
+
v_align: v_align,
|
143
|
+
left_pad: left_pad,
|
144
|
+
right_pad: right_pad,
|
145
|
+
top_pad: top_pad,
|
146
|
+
bottom_pad: bottom_pad,
|
147
|
+
delta_x: delta_x,
|
148
|
+
delta_y: delta_y,
|
149
|
+
font_name: font_name,
|
150
|
+
font_style: font_style,
|
151
|
+
font_size: font_size,
|
152
|
+
# Input attributes
|
153
|
+
in_file: in_file,
|
154
|
+
nl_sep: nl_sep,
|
155
|
+
label_sep: label_sep,
|
156
|
+
copies: copies,
|
157
|
+
# Output attributes
|
158
|
+
printer: printer,
|
159
|
+
out_file: out_file,
|
160
|
+
print_command: print_command,
|
161
|
+
view_command: view_command,
|
162
|
+
view: view,
|
163
|
+
template: template,
|
164
|
+
verbose: verbose,
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
# Update the fields of this Option instance by merging in the values in
|
169
|
+
# hsh into self. Ignore any keys in hsh not corresponding to a setter for
|
170
|
+
# an Options object.
|
171
|
+
def merge!(hsh)
|
172
|
+
# Convert any separator hyphens in the hash keys to underscores
|
173
|
+
hsh = hsh.to_hash.transform_keys { |key| key.to_s.gsub('-', '_').to_sym }
|
174
|
+
new_hash = to_hash.merge(hsh)
|
175
|
+
new_hash.each_pair do |k, val|
|
176
|
+
setter = "#{k}=".to_sym
|
177
|
+
next unless respond_to?(setter)
|
178
|
+
|
179
|
+
send(setter, val)
|
180
|
+
end
|
181
|
+
self
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labrat
|
4
|
+
def self.read_label_texts(fname, nlsep)
|
5
|
+
file =
|
6
|
+
if fname
|
7
|
+
ofname = fname
|
8
|
+
fname = File.expand_path(fname)
|
9
|
+
unless File.readable?(fname)
|
10
|
+
raise "Cannot open label file '#{ofname}' for reading"
|
11
|
+
end
|
12
|
+
File.open(fname)
|
13
|
+
else
|
14
|
+
$stdin
|
15
|
+
end
|
16
|
+
|
17
|
+
texts = []
|
18
|
+
label = nil
|
19
|
+
file.each do |line|
|
20
|
+
next if line =~ /\A#/
|
21
|
+
|
22
|
+
if line =~ /\A\s*\z/
|
23
|
+
# At blank line record any accumulated label into texts, but remove
|
24
|
+
# the nlsep from the end.
|
25
|
+
if label
|
26
|
+
texts << label.sub(/#{Regexp.quote(nlsep)}\z/, '')
|
27
|
+
label = nil
|
28
|
+
end
|
29
|
+
else
|
30
|
+
# Append a non-blank line to the current label, creating it if
|
31
|
+
# necessary.
|
32
|
+
label ||= +''
|
33
|
+
label << line.chomp + nlsep
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# Last label in the file.
|
37
|
+
texts << label.sub(/#{Regexp.quote(nlsep)}\z/, '') if label
|
38
|
+
texts
|
39
|
+
end
|
40
|
+
end
|
data/lib/labrat.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'fat_core/enumerable'
|
6
|
+
require 'prawn'
|
7
|
+
require 'prawn/measurement_extensions'
|
8
|
+
require 'optparse'
|
9
|
+
require 'yaml'
|
10
|
+
require 'pp'
|
11
|
+
|
12
|
+
require_relative "labrat/version"
|
13
|
+
require_relative "labrat/errors"
|
14
|
+
require_relative "labrat/hash"
|
15
|
+
require_relative "labrat/options"
|
16
|
+
require_relative "labrat/arg_parser"
|
17
|
+
require_relative "labrat/label"
|
18
|
+
require_relative "labrat/config"
|
19
|
+
require_relative "labrat/label_db"
|
20
|
+
require_relative "labrat/read_files"
|
data/lib/lisp/labrat.el
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
;;;; labrat -- Print labels using labrat from within a buffer.
|
2
|
+
|
3
|
+
;;; Commentary:
|
4
|
+
|
5
|
+
;; Lisp commands to print labels from within Emacs a buffer by invoking
|
6
|
+
;; labrat, a Ruby application desgined for printing labels from the
|
7
|
+
;; command-line rather than through a GUI. You must have labrat installed
|
8
|
+
;; first. See https://github.com/ddoherty03/labrat for details.
|
9
|
+
|
10
|
+
;;; Code:
|
11
|
+
|
12
|
+
(require 'thingatpt)
|
13
|
+
(require 's)
|
14
|
+
(require 'dash)
|
15
|
+
|
16
|
+
(defcustom labrat-executable "labrat"
|
17
|
+
"Executable for labrat.
|
18
|
+
|
19
|
+
If the executable is not in your variable `exec-path', set this
|
20
|
+
to the full path name of the executable,
|
21
|
+
e.g. ~/.rbenv/shims/labrat, for an rbenv ruby installation."
|
22
|
+
:type 'string
|
23
|
+
:group 'labrat)
|
24
|
+
|
25
|
+
(defcustom labrat-nl-sep "++"
|
26
|
+
"String to mark newlines in label text.
|
27
|
+
|
28
|
+
If you change this, you need to make a corresponding change in your
|
29
|
+
labrat configuration at ~/.config/labrat/config.yml."
|
30
|
+
:type 'string
|
31
|
+
:group 'labrat)
|
32
|
+
|
33
|
+
(defcustom labrat-label-sep "]*["
|
34
|
+
"String to mark the separation between labels on the labrat command-line.
|
35
|
+
|
36
|
+
If you change this, you need to make a corresponding change in your
|
37
|
+
labrat configuration at ~/.config/labrat/config.yml."
|
38
|
+
:type 'string
|
39
|
+
:group 'labrat)
|
40
|
+
|
41
|
+
(defun labrat/pars-in-region ()
|
42
|
+
"Return a string of paragraphs in region, separated by `labrat-label-sep'.
|
43
|
+
|
44
|
+
If the region is not active, just return the paragraph at or before point as
|
45
|
+
is done by `labrat-par-at-point'. In either case strip comment lines."
|
46
|
+
(if (region-active-p)
|
47
|
+
(progn
|
48
|
+
(let ((beg (region-beginning))
|
49
|
+
(end (save-excursion
|
50
|
+
(progn (goto-char (region-end))
|
51
|
+
(forward-paragraph)
|
52
|
+
(point))))
|
53
|
+
(pars ""))
|
54
|
+
(progn
|
55
|
+
(save-excursion
|
56
|
+
(goto-char beg)
|
57
|
+
(while (< (point) end)
|
58
|
+
(forward-paragraph)
|
59
|
+
(setq pars (s-concat pars (labrat/par-at-point) labrat-label-sep))))
|
60
|
+
(s-chop-prefix labrat-label-sep (s-chop-suffix labrat-label-sep pars)))))
|
61
|
+
(labrat/par-at-point)))
|
62
|
+
|
63
|
+
(defun labrat/par-at-point ()
|
64
|
+
"Return the paragraph at or before point.
|
65
|
+
|
66
|
+
Similar to the command `thing-at-point' for paragraph, but look
|
67
|
+
for preceding paragraph even if there are several blank lines
|
68
|
+
before point, trim white space, comments, and properties from the
|
69
|
+
result."
|
70
|
+
(save-excursion
|
71
|
+
(unless (looking-at ".+")
|
72
|
+
(re-search-backward "^.+$" nil 'to-bob))
|
73
|
+
(s-replace "\n" labrat-nl-sep
|
74
|
+
(labrat/remove-comments
|
75
|
+
(s-trim (thing-at-point 'paragraph t))))))
|
76
|
+
|
77
|
+
(defun labrat/remove-comments (str)
|
78
|
+
"Remove any lines from STR that start with the comment character '#'.
|
79
|
+
|
80
|
+
If STR consists of multiple new-line separated lines, the lines
|
81
|
+
that start with '#' are removed, and the remaining lines
|
82
|
+
returned"
|
83
|
+
(s-join "\n" (--remove (string-match "^#" it) (s-split "\n" str))))
|
84
|
+
|
85
|
+
(defun labrat-view ()
|
86
|
+
"View the paragraph at or before point as a label with labrat.
|
87
|
+
|
88
|
+
This invokes the \"labrat -V\ <label>\" command with the
|
89
|
+
paragraph at or before point inserted in the <label> position,
|
90
|
+
but with each new-line replaced with the value of the variable
|
91
|
+
labrat-nl-sep, '++' by default."
|
92
|
+
(interactive)
|
93
|
+
(call-process labrat-executable nil nil nil
|
94
|
+
"-V" (labrat/pars-in-region)))
|
95
|
+
|
96
|
+
(defun labrat-print ()
|
97
|
+
"Print the paragraph at or before point as a label with labrat.
|
98
|
+
|
99
|
+
This invokes the \"labrat -P <label>\" command with the paragraph
|
100
|
+
at or before point inserted in the <label> position, but with
|
101
|
+
each new-line replaced with the value of the variable
|
102
|
+
labrat-nl-sep, '++' by default."
|
103
|
+
(interactive)
|
104
|
+
(call-process labrat-executable nil nil nil
|
105
|
+
(labrat/pars-in-region)))
|
106
|
+
|
107
|
+
(provide 'labrat)
|
108
|
+
;;; labrat.el ends here
|