law_doc 0.1.15

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.
@@ -0,0 +1,452 @@
1
+ module LawDoc
2
+ # LawDoc class for representing a legal document, a pleading, motion, or
3
+ # other litigation-related document.
4
+ class Document
5
+ attr_reader :preamble, :postamble
6
+
7
+ include InitTypes
8
+
9
+ # TODO: since most of the setter-getter methods, like signers, etc, depend on there being a kase and
10
+ # on_behalf_of set, perhaps those arguments should be mandatory and set before anything else.
11
+
12
+ def initialize(title: nil, kase: nil,
13
+ sig_date: nil,
14
+ on_behalf_of: nil,
15
+ signers: nil, signer: nil,
16
+ server: nil,
17
+ service_date: nil)
18
+ @kase = kase
19
+ @title = title
20
+ @on_behalf_of = []
21
+ self.on_behalf_of = on_behalf_of if on_behalf_of
22
+ @signers = []
23
+ @signers << signer if signer
24
+ @signers += signers if signers
25
+ @sig_date = init_date(sig_date) || Date.today
26
+ @service_date = service_date || @sig_date
27
+ @server = init_person(server)
28
+ @server = @signers.first if @server.nil?
29
+ @preamble = <<~'PRE'
30
+ \makeatletter
31
+ \usepackage{longtable}
32
+ % \blkwd is meant to be the width of one block, such as a lawyers block,
33
+ % such that two side-by-side in a table take up 95% of the text width
34
+ \newlength{\blkwd}%
35
+ \setlength{\blkwd}{(\textwidth - \tabcolsep * 4) * \real{0.95} / 2}%
36
+ PRE
37
+ @postamble = <<~'POST'
38
+ \makeatother
39
+ POST
40
+ yield self if block_given?
41
+ end
42
+
43
+ def to_h
44
+ {
45
+ title: title, kase: kase, sig_date: sig_date,
46
+ signers: signers, service_date: service_date,
47
+ on_behalf_of: on_behalf_of
48
+ }
49
+ end
50
+
51
+ include Comparable
52
+
53
+ def <=>(other)
54
+ to_h <=> other.to_h
55
+ end
56
+
57
+ #######################################################################
58
+ # Attributes
59
+ #######################################################################
60
+
61
+ # Set or return title
62
+ def title(*title)
63
+ title.empty? ? @title : @title = title.first.to_s
64
+ end
65
+
66
+ # Set or return kase
67
+ def kase(*kase)
68
+ kase.empty? ? @kase : @kase = init_case(kase.first)
69
+ end
70
+
71
+ # Set or return sig_date
72
+ def sig_date(*sig_date)
73
+ if sig_date.empty?
74
+ @sig_date
75
+ else
76
+ @sig_date = init_date(sig_date.first, field: 'sig_date')
77
+ end
78
+ end
79
+
80
+ # Set or return service_date
81
+ def service_date(*service_date)
82
+ if service_date.empty?
83
+ @service_date
84
+ else
85
+ @service_date = init_date(service_date.first, field: 'service_date')
86
+ end
87
+ end
88
+
89
+ # Set or return signers
90
+ def signers(*signers)
91
+ if signers.empty?
92
+ @signers
93
+ else
94
+ signers.each do |sgnr|
95
+ signer(sgnr)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Set a single signer
101
+ def signer(person = nil)
102
+ return @signers.first if person.nil?
103
+
104
+ person = init_person(person, field: 'signers')
105
+ raise ArgumentError, 'signer must be a Person' unless person.is_a?(Person)
106
+
107
+ # Confirm that signer is an attorney for the party submitting the document before adding to signers.
108
+ signer_parties = on_behalf_of
109
+ if signer_parties.empty?
110
+ raise ArgumentError, "must set 'on_behalf_of' before signer"
111
+ end
112
+
113
+ parties_lawyers = signer_parties.map(&:lawyers).flatten.uniq
114
+ unless parties_lawyers.include?(person)
115
+ raise ArgumentError, "signer #{person.name} must be a lawyer for on_behalf_of party "
116
+ end
117
+
118
+ @signers ||= []
119
+ @signers << person
120
+ @signers = @signers.compact.uniq
121
+ end
122
+
123
+ # Set or return server
124
+ def server(*server)
125
+ case server.size
126
+ when 0
127
+ @server
128
+ else
129
+ @server = init_person(server.first)
130
+ end
131
+ end
132
+
133
+ # Set party or parties on behalf of whom the document is being
134
+ # submitted.
135
+ def on_behalf_of=(role_or_parties)
136
+ if role_or_parties.is_a?(Enumerable) && role_or_parties.size == 1
137
+ role_or_parties = role_or_parties.first
138
+ end
139
+ @on_behalf_of =
140
+ case role_or_parties
141
+ when String, Party
142
+ rop_to_parties(role_or_parties)
143
+ when Array
144
+ parties = []
145
+ role_or_parties.each do |rop|
146
+ ptys = rop_to_parties(rop)
147
+ ptys.each do |pty|
148
+ unless kase.parties.include?(pty)
149
+ raise ArgumentError, "'#{pty}' is not a party to #{kase}"
150
+ end
151
+
152
+ parties << pty
153
+ end
154
+ end
155
+ parties
156
+ else
157
+ raise ArgumentError,
158
+ "cannot convert '#{role_or_parties}' of class #{role_or_parties.class} to Parties"
159
+ end
160
+ end
161
+
162
+ # Set party or parties on behalf of whom the document is being
163
+ # submitted.
164
+ def on_behalf_of(*role_or_parties)
165
+ if role_or_parties.empty?
166
+ @on_behalf_of
167
+ else
168
+ unless kase
169
+ raise UserError,
170
+ 'must set kase /before/ setting on_behalf_of'
171
+ end
172
+ self.on_behalf_of = role_or_parties
173
+ end
174
+ end
175
+
176
+ # Parties other than those on whose behalf this document is being filed.
177
+ def not_on_behalf_of
178
+ kase.parties - on_behalf_of
179
+ end
180
+
181
+ # Return all parties with the given lawyer.
182
+ def parties_with_lawyer(lwy)
183
+ parties = Set.new
184
+ kase.parties.each do |pty|
185
+ parties << pty if pty.lawyers.include?(lwy)
186
+ end
187
+ parties
188
+ end
189
+
190
+ # Return all lawyers for the given party or parties.
191
+ def lawyers_for(parties)
192
+ parties = [parties] if parties.is_a?(Party)
193
+ lawyers = Set.new
194
+ parties.each do |pty|
195
+ pty.lawyers.each do |lwy|
196
+ lawyers << lwy
197
+ end
198
+ end
199
+ lawyers.to_a.compact
200
+ end
201
+
202
+ # Return all lawyers representing the party or parties on whose behalf this
203
+ # document is being filed.
204
+ def our_lawyers
205
+ lawyers_for(on_behalf_of)
206
+ end
207
+
208
+ # Return all lawyers /not/ representing the party or parties on whose behalf
209
+ # this document is being filed.
210
+ def their_lawyers
211
+ lawyers_for(not_on_behalf_of)
212
+ end
213
+
214
+ def tex_lawdoc_macros
215
+ result = preamble
216
+ result += kase.tex_lawdoc_macros
217
+ result += tex_lawdoc_document
218
+ result += tex_lawdoc_our_role
219
+ result += tex_lawdoc_their_role
220
+ result += tex_lawdoc_signers_block
221
+ result += tex_lawdoc_signatures
222
+ result += tex_lawdoc_our_lawyers_block
223
+ result += tex_lawdoc_their_lawyers_block
224
+ result += tex_lawdoc_service_list
225
+ result += postamble
226
+ result
227
+ end
228
+
229
+ # Macro to define lawyer information block for lawyers representing the
230
+ # party on whose behalf the document is being file.
231
+ # the pre-caption area.
232
+ def tex_lawdoc_our_lawyers_block
233
+ lwys = lawyers_for(on_behalf_of)
234
+ "\\OurLawyersBlock{%\n#{tex_lawdoc_lawyers_block(lwys)}}\n"
235
+ end
236
+
237
+ # Party role of party on whose behalf document is being filed, e.g.,
238
+ # "Plaintiff", "Defendant", etc.
239
+ def tex_lawdoc_our_role
240
+ ptys = on_behalf_of
241
+ "\\OurRole{#{kase.party_roles(ptys)}}\n"
242
+ end
243
+
244
+ # Macro to define lawyer information block for lawyers NOT representing
245
+ # the party on whose behalf the document is being file. the pre-caption
246
+ # area.
247
+ def tex_lawdoc_their_lawyers_block
248
+ lwys = lawyers_for(not_on_behalf_of)
249
+ "\\TheirLawyersBlock{%\n#{tex_lawdoc_lawyers_block(lwys)}}\n"
250
+ end
251
+
252
+ # Party role of party of opposing counsel, e.g., "Plaintiff", "Defendant",
253
+ # etc.
254
+ def tex_lawdoc_their_role
255
+ ptys = not_on_behalf_of
256
+ "\\TheirRole{#{kase.party_roles(ptys)}}\n"
257
+ end
258
+
259
+ # Macro to define lawyer information block, not for the signatures, but
260
+ # where a list of the lawyers signing the document are needed, e.g., in
261
+ # the pre-caption area.
262
+ def tex_lawdoc_signers_block
263
+ "\\SignersBlock{%\n#{tex_lawdoc_lawyers_block(signers)}}\n"
264
+ end
265
+
266
+ # Macro to define lawyer information block for lawyer serving the document
267
+ # to opposing counsel. Not much needed in the era of electronic filing.
268
+ def tex_lawdoc_service_list
269
+ lwys = lawyers_for(not_on_behalf_of)
270
+ "\\ServiceList{%\n#{tex_lawdoc_lawyers_block(lwys)}}\n"
271
+ end
272
+
273
+ # Longtable that places lawyer info block for the given array of lawyers
274
+ # in a table two across.
275
+ def tex_lawdoc_lawyers_block(the_lwys)
276
+ # Form the output string
277
+ result = <<~'LTPRE'
278
+ {\setlength{\LTpre}{0pt}
279
+ \setlength{\LTpost}{0pt}
280
+ \setlength{\extrarowheight}{12pt}%
281
+ \begin{small}
282
+ \singlespace
283
+ \begin{longtable}[l]{ll}%
284
+ LTPRE
285
+ the_lwys.groups_of(2).each do |_k, lwys|
286
+ l1, l2 = lwys
287
+ bar_state = kase.court.state
288
+ result += "\\parbox[t]{\\blkwd}{\n"
289
+ result += l1.tex_block(bar_state: bar_state, full: true).to_s
290
+ result += "}&\n"
291
+ next unless l2
292
+
293
+ result += "\\parbox[t]{\\blkwd}{\n"
294
+ result += l2.tex_block(bar_state: bar_state, full: true).to_s
295
+ result += "}\\\\\n"
296
+ end
297
+ result += <<~'LTPOST'
298
+ \end{longtable}
299
+ \end{small}}
300
+ LTPOST
301
+ end
302
+
303
+ private
304
+
305
+ # Return string for lawyer's capacity in kase associated with this
306
+ # document.
307
+ def capacity(lawyer, full: false)
308
+ return '' unless lawyer.is_a?(Lawyer)
309
+
310
+ pname = kase.lawyer_parties_names(lawyer)
311
+ prole = kase.lawyer_roles(lawyer)
312
+ if full
313
+ "Attorney for #{prole}, #{pname}"
314
+ else
315
+ "Attorney for #{prole}"
316
+ end
317
+ end
318
+
319
+ # Translate a string or Party to an array of Party's, using matching on the
320
+ # role for a string.
321
+ def rop_to_parties(rop)
322
+ case rop
323
+ when String
324
+ kase.parties_matching_role(rop)
325
+ when Party
326
+ unless kase.parties.include?(rop)
327
+ raise ArgumentError, "'#{rop}' is not a party to #{kase}"
328
+ end
329
+
330
+ [rop]
331
+ else
332
+ raise ArgumentError, "cannot convert '#{rop}' to a Party"
333
+ end
334
+ end
335
+
336
+ def tex_lawdoc_document
337
+ result = <<~MACS
338
+ \\DocTitle{#{title.tq}}
339
+ \\SignerName{#{signer.name.tq}}
340
+ \\SignerAddress{#{signer.address.tex_block}}
341
+ \\SignerBarState{#{kase.court.state.tq}}
342
+ \\SignerBarNumber{#{signer.bar_numbers[kase.court.state]}}
343
+ \\SignerPhone{#{signer.phone.to_s.tq}}
344
+ \\SignerFax{#{signer.fax.to_s.tq}}
345
+ \\SignerEmail{#{signer.email.to_s.tq}}
346
+ \\SignerSig{#{signer.sig}}
347
+ \\SignerESig{#{signer.esig.tq}}
348
+ \\SigDate{#{sig_date.eng}}
349
+ MACS
350
+ if server
351
+ result += <<~MACS
352
+ \\ServerName{#{server.name.tq}}
353
+ \\ServerSig{#{server.sig}}
354
+ \\ServerESig{#{server.esig.tq}}
355
+ \\ServiceDate{#{service_date.eng}}
356
+ MACS
357
+ end
358
+ result
359
+ end
360
+
361
+ # Longtable that places lawyer signature block for the given array of
362
+ # lawyers in a table two across.
363
+ def tex_lawdoc_signatures
364
+ state = kase.court.state
365
+ if signers.size == 1
366
+ l1 = signers.first
367
+ cap = capacity(l1, full: true)
368
+ full_signature = "#{l1.tex_sigblock(capacity: cap, sig: l1.sig, bar_state: state, full: true)}\n"
369
+ full_esignature = "#{l1.tex_sigblock(capacity: cap, sig: l1.esig, bar_state: state, full: true)}\n"
370
+ full_blank_signature = "#{l1.tex_sigblock(capacity: cap, sig: '', bar_state: state, full: true)}\n"
371
+ signature = "#{l1.tex_sigblock(capacity: cap, sig: l1.sig, bar_state: state)}\n"
372
+ esignature = "#{l1.tex_sigblock(capacity: cap, sig: l1.esig, bar_state: state)}\n"
373
+ blank_signature = "#{l1.tex_sigblock(capacity: cap, sig: '', bar_state: state)}\n"
374
+ return <<~MACS
375
+ \\Signature{%
376
+ \\ifthenelse{\\boolean{sign}}%
377
+ {#{signature}}
378
+ {\\ifthenelse{\\boolean{esign}}%
379
+ {#{esignature}}
380
+ {#{blank_signature}}}}
381
+ \\FullSignature{%
382
+ \\ifthenelse{\\boolean{sign}}%
383
+ {#{full_signature}}
384
+ {\\ifthenelse{\\boolean{esign}}%
385
+ {#{full_esignature}}
386
+ {#{full_blank_signature}}}}
387
+ MACS
388
+ end
389
+
390
+ # Form the output string
391
+ lt_pre = <<~'PRESIGS'
392
+ {
393
+ \setlength{\parindent}{0pt}%
394
+ PRESIGS
395
+ lt_post = <<~'POSTSIGS'
396
+ }
397
+ POSTSIGS
398
+ # Six different body for variations, (1) full or normal, and (2) sig,
399
+ # esig, or blank signatures.
400
+ full_sig_body = ''
401
+ full_esig_body = ''
402
+ full_blanksig_body = ''
403
+ sig_body = ''
404
+ esig_body = ''
405
+ blanksig_body = ''
406
+ signers.groups_of(2).each do |_k, lwys|
407
+ l1, l2 = lwys
408
+ cap = capacity(l1)
409
+ full_cap = capacity(l1, full: true)
410
+ f = '\hfill'
411
+ # f = '&'
412
+ full_sig_body += "#{l1.tex_plainsigblock(capacity: full_cap, sig: l1.sig, bar_state: state, full: true)}#{f}\n"
413
+ full_esig_body += "#{l1.tex_plainsigblock(capacity: full_cap, sig: l1.esig, bar_state: state, full: true)}#{f}\n"
414
+ full_blanksig_body += "#{l1.tex_plainsigblock(capacity: full_cap, sig: '', bar_state: state, full: true)}#{f}\n"
415
+ sig_body += "#{l1.tex_plainsigblock(capacity: cap, sig: l1.sig, bar_state: state)}#{f}\n"
416
+ esig_body += "#{l1.tex_plainsigblock(capacity: cap, sig: l1.esig, bar_state: state)}#{f}\n"
417
+ blanksig_body += "#{l1.tex_plainsigblock(capacity: cap, sig: '', bar_state: state)}#{f}\n"
418
+ next unless l2
419
+
420
+ cap = capacity(l2)
421
+ f = "\n\n\\vspace{14pt}\n"
422
+ full_cap = capacity(l2, full: true)
423
+ full_sig_body += "#{l2.tex_plainsigblock(capacity: full_cap, sig: l2.sig, bar_state: state, full: true)}#{f}"
424
+ full_esig_body += "#{l2.tex_plainsigblock(capacity: full_cap, sig: l2.esig, bar_state: state, full: true)}#{f}"
425
+ full_blanksig_body += "#{l2.tex_plainsigblock(capacity: full_cap, sig: '', bar_state: state, full: true)}#{f}"
426
+ sig_body += "#{l2.tex_plainsigblock(capacity: cap, sig: l2.sig, bar_state: state)}#{f}"
427
+ esig_body += "#{l2.tex_plainsigblock(capacity: cap, sig: l2.esig, bar_state: state)}#{f}"
428
+ blanksig_body += "#{l2.tex_plainsigblock(capacity: cap, sig: '', bar_state: state)}#{f}"
429
+ end
430
+ full_signature = lt_pre + full_sig_body + lt_post
431
+ full_esignature = lt_pre + full_esig_body + lt_post
432
+ full_blank_signature = lt_pre + full_blanksig_body + lt_post
433
+ signature = lt_pre + sig_body + lt_post
434
+ esignature = lt_pre + esig_body + lt_post
435
+ blank_signature = lt_pre + blanksig_body + lt_post
436
+ <<~MACS
437
+ \\Signature{%
438
+ \\ifthenelse{\\boolean{sign}}%
439
+ {#{signature}}
440
+ {\\ifthenelse{\\boolean{esign}}%
441
+ {#{esignature}}
442
+ {#{blank_signature}}}}
443
+ \\FullSignature{%
444
+ \\ifthenelse{\\boolean{sign}}%
445
+ {#{full_signature}}
446
+ {\\ifthenelse{\\boolean{esign}}%
447
+ {#{full_esignature}}
448
+ {#{full_blank_signature}}}}
449
+ MACS
450
+ end
451
+ end
452
+ end
@@ -0,0 +1,2 @@
1
+ class UserError < RuntimeError; end
2
+ class LogicError < RuntimeError; end
@@ -0,0 +1,182 @@
1
+ module LawDoc
2
+ module InitTypes
3
+ def init_date(dt, field: 'date')
4
+ case dt
5
+ when Date
6
+ dt
7
+ when String
8
+ Date.parse(dt)
9
+ when NilClass
10
+ nil
11
+ else
12
+ msg = "cannot initialize #{field} with #{dt.class.name}"
13
+ raise ArgumentError, msg
14
+ end
15
+ end
16
+
17
+ def init_address(addr, field: 'address')
18
+ case addr
19
+ when Address
20
+ addr
21
+ when Hash
22
+ if addr.empty?
23
+ nil
24
+ else
25
+ Address.new(**addr)
26
+ end
27
+ when NilClass
28
+ nil
29
+ else
30
+ msg = "cannot initialize #{field} with #{addr.class.name}"
31
+ raise ArgumentError, msg
32
+ end
33
+ end
34
+
35
+ def init_phone(phn, field: 'phone')
36
+ case phn
37
+ when Phone
38
+ phn
39
+ when String
40
+ Phone.new(phn)
41
+ when Hash
42
+ if phn.empty?
43
+ nil
44
+ else
45
+ Phone.new(**phn)
46
+ end
47
+ when NilClass
48
+ nil
49
+ else
50
+ msg = "cannot initialize #{field} with #{phn.class.name}"
51
+ raise ArgumentError, msg
52
+ end
53
+ end
54
+
55
+ def init_person(psn, field: 'person')
56
+ case psn
57
+ when Lawyer
58
+ psn
59
+ when Person
60
+ psn
61
+ when Hash
62
+ return nil if psn.empty?
63
+
64
+ if psn.symbolize_keys.keys.include?(:bar_numbers)
65
+ Lawyer.new(**psn)
66
+ else
67
+ Person.new(**psn)
68
+ end
69
+ when NilClass
70
+ nil
71
+ else
72
+ msg = "cannot initialize #{field} with #{psn.class.name}"
73
+ raise ArgumentError, msg
74
+ end
75
+ end
76
+
77
+ def init_lawyer(lwy, field: 'lawyer')
78
+ case lwy
79
+ when Lawyer
80
+ lwy
81
+ when Hash
82
+ return nil if lwy.empty?
83
+
84
+ Lawyer.new(**lwy)
85
+ when NilClass
86
+ nil
87
+ else
88
+ msg = "cannot initialize #{field} with #{lwy.class.name}"
89
+ raise ArgumentError, msg
90
+ end
91
+ end
92
+
93
+ def init_lawyers(lwys)
94
+ case lwys
95
+ when Lawyer
96
+ [lwys]
97
+ when Array
98
+ lwys.map { |l| init_lawyer(l) }.compact
99
+ when NilClass
100
+ []
101
+ else
102
+ msg = "cannot initialize lawyers with #{lwys.class.name}"
103
+ raise ArgumentError, msg
104
+ end
105
+ end
106
+
107
+ def init_judge(jdg, field: 'judge')
108
+ case jdg
109
+ when Judge
110
+ jdg
111
+ when Hash
112
+ return nil if jdg.empty?
113
+ Judge.new(**jdg)
114
+ when NilClass
115
+ nil
116
+ else
117
+ msg = "cannot initialize #{field} with #{jdg.class.name}"
118
+ raise ArgumentError, msg
119
+ end
120
+ end
121
+
122
+ def init_court(crt, field: 'court')
123
+ case crt
124
+ when Court
125
+ crt
126
+ when Hash
127
+ return nil if crt.empty?
128
+ Court.new(**crt)
129
+ when NilClass
130
+ nil
131
+ else
132
+ msg = "cannot initialize #{field} with #{crt.class.name}"
133
+ raise ArgumentError, msg
134
+ end
135
+ end
136
+
137
+ def init_party(pty, field: 'party')
138
+ case pty
139
+ when Party
140
+ pty
141
+ when Hash
142
+ return nil if pty.empty?
143
+
144
+ Party.new(**pty)
145
+ when NilClass
146
+ nil
147
+ else
148
+ msg = "cannot initialize #{field} with #{pty.class.name}"
149
+ raise ArgumentError, msg
150
+ end
151
+ end
152
+
153
+ def init_parties(ptys)
154
+ case ptys
155
+ when Party, Hash
156
+ ptys.map { |p| init_party(p) }.compact
157
+ when Array
158
+ ptys.map { |p| init_party(p) }.compact
159
+ when NilClass
160
+ []
161
+ else
162
+ msg = "cannot initialize parties with #{ptys.class.name}"
163
+ raise ArgumentError, msg
164
+ end
165
+ end
166
+
167
+ def init_case(cs, field: 'case')
168
+ case cs
169
+ when Case
170
+ cs
171
+ when Hash
172
+ return nil if cs.empty?
173
+ Case.new(**cs)
174
+ when NilClass
175
+ nil
176
+ else
177
+ msg = "cannot initialize #{field} with #{cs.class.name}"
178
+ raise ArgumentError, msg
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,28 @@
1
+ module LawDoc
2
+ class Judge < Person
3
+ attr_reader :title
4
+ attr_accessor :initials
5
+
6
+ def initialize(initials: nil,
7
+ title: 'Judge',
8
+ **other)
9
+ other[:hon] = 'Hon.' if other[:hon].blank?
10
+ super(**other)
11
+ @initials =
12
+ if initials
13
+ initials
14
+ else
15
+ self.initials = +''
16
+ self.initials += first[0] if first
17
+ self.initials += middle[0] if middle
18
+ self.initials += last[0] if last
19
+ end
20
+ @title = title
21
+ end
22
+
23
+ def to_h
24
+ hh = super
25
+ hh.merge(initials: initials, title: title)
26
+ end
27
+ end
28
+ end