labrat 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- 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
|