mo2tex 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/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/LICENSE +674 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/config/courses.yml +165 -0
- data/config/templates/latex/body.tex.erb +5 -0
- data/config/templates/latex/header.tex +20 -0
- data/config/templates/latex/trailer.tex +1 -0
- data/config/templates/pic/body.pic.erb +14 -0
- data/config/templates/pic/header.pic +13 -0
- data/config/templates/pic/objects.pic +46 -0
- data/config/templates/pic/trailer.pic.erb +3 -0
- data/exe/m2c +16 -0
- data/exe/m2l +16 -0
- data/exe/m2p +16 -0
- data/lib/mo2tex/calendar.rb +54 -0
- data/lib/mo2tex/course.rb +198 -0
- data/lib/mo2tex/course_helper.rb +123 -0
- data/lib/mo2tex/datetime_helper.rb +29 -0
- data/lib/mo2tex/day_schedule.rb +135 -0
- data/lib/mo2tex/event.rb +77 -0
- data/lib/mo2tex/exceptions.rb +21 -0
- data/lib/mo2tex/exclude_date.rb +68 -0
- data/lib/mo2tex/exclusion_manager.rb +59 -0
- data/lib/mo2tex/extensions/date_time.rb +66 -0
- data/lib/mo2tex/latex.rb +66 -0
- data/lib/mo2tex/main.rb +60 -0
- data/lib/mo2tex/pic.rb +315 -0
- data/lib/mo2tex/school.rb +68 -0
- data/lib/mo2tex/version.rb +5 -0
- data/lib/mo2tex.rb +31 -0
- data/sig/mo2tex.rbs +4 -0
- data/tmp/.gitkeep +1 -0
- metadata +136 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Mo2tex
|
6
|
+
|
7
|
+
module CourseHelper
|
8
|
+
|
9
|
+
def normalize_title(t)
|
10
|
+
return t.gsub(/_/, ' ')
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
# class Course
|
16
|
+
|
17
|
+
# include CourseHelper
|
18
|
+
|
19
|
+
# attr :title, :hours, :type, :students, :start
|
20
|
+
|
21
|
+
# def initialize(title, desc)
|
22
|
+
# @title = normalize_title(title)
|
23
|
+
# @hours = desc['ore']
|
24
|
+
# @type = desc['tipologia']
|
25
|
+
# @students = desc['studenti']
|
26
|
+
# @start = DateTime.parse(desc['inizio'].to_s)
|
27
|
+
# end
|
28
|
+
|
29
|
+
# def generate(cal)
|
30
|
+
# result = latex_title
|
31
|
+
# evs = cal.lookup(self.title)
|
32
|
+
# raise UnknownCourse, "#{self.title}" unless evs
|
33
|
+
# tot_nlessons = self.total_number_of_lessons(evs)
|
34
|
+
# nless = 0
|
35
|
+
# evs.each do
|
36
|
+
# |ev|
|
37
|
+
# ev.repeat do
|
38
|
+
# |sev, title|
|
39
|
+
# nless += 1
|
40
|
+
# result += (sev.to_latex + " & Lezione n.#{nless}/#{tot_nlessons}\\\\\n")
|
41
|
+
# break if nless > tot_nlessons
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# return result
|
45
|
+
# end
|
46
|
+
|
47
|
+
# def total_course_dur_in_seconds
|
48
|
+
# return self.academic_hours * 3600
|
49
|
+
# end
|
50
|
+
|
51
|
+
# def total_number_of_lessons(evs)
|
52
|
+
# tot = 0
|
53
|
+
# nless = 0
|
54
|
+
# evs.each do
|
55
|
+
# |ev|
|
56
|
+
# ev.repeat do
|
57
|
+
# |sev, title|
|
58
|
+
# tot += sev.dur
|
59
|
+
# nless += 1
|
60
|
+
# break if tot > self.total_course_dur_in_seconds
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# return nless
|
64
|
+
# end
|
65
|
+
|
66
|
+
# class <<self
|
67
|
+
|
68
|
+
# def create(title, desc)
|
69
|
+
# result = nil
|
70
|
+
# case desc['tipologia']
|
71
|
+
# when 'collettiva'
|
72
|
+
# result = CollectiveCourse.new(title, desc)
|
73
|
+
# when 'individuale'
|
74
|
+
# result = IndividualCourse.new(title, desc)
|
75
|
+
# else
|
76
|
+
# raise UnknownCourseType, "Unknown type #{desc['tipologia']}"
|
77
|
+
# end
|
78
|
+
# return result
|
79
|
+
# end
|
80
|
+
|
81
|
+
# end
|
82
|
+
|
83
|
+
# def num_students
|
84
|
+
# return self.students.size
|
85
|
+
# end
|
86
|
+
|
87
|
+
# def total_hours
|
88
|
+
# raise PureVirtualMethodCalled, 'Course#total_hours'
|
89
|
+
# end
|
90
|
+
|
91
|
+
# def academic_hours
|
92
|
+
# return self.total_hours
|
93
|
+
# end
|
94
|
+
|
95
|
+
# private
|
96
|
+
|
97
|
+
# def latex_title
|
98
|
+
# return "\\multicolumn{3}{\\bfseries #{self.title}}\\\\\n"
|
99
|
+
# end
|
100
|
+
|
101
|
+
# end
|
102
|
+
|
103
|
+
# class CollectiveCourse < Course
|
104
|
+
|
105
|
+
# def total_hours
|
106
|
+
# return self.hours
|
107
|
+
# end
|
108
|
+
|
109
|
+
# end
|
110
|
+
|
111
|
+
# class IndividualCourse < Course
|
112
|
+
|
113
|
+
# def total_hours
|
114
|
+
# return self.hours * self.num_students
|
115
|
+
# end
|
116
|
+
|
117
|
+
# def academic_hours
|
118
|
+
# return (self.total_hours * 0.75)
|
119
|
+
# end
|
120
|
+
|
121
|
+
# end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mo2tex
|
4
|
+
|
5
|
+
module DateTimeHelper
|
6
|
+
|
7
|
+
def datetime_create(day, t)
|
8
|
+
raise DayIsNotDateTime, "#{day}" unless day.class == DateTime
|
9
|
+
raise TimeIsNotString, "#{t}" unless t.class == String
|
10
|
+
(hours, minutes) = t.split(':').map { |n| n.to_i }
|
11
|
+
result = DateTime.new(day.year, day.month, day.day, hours, minutes, 0)
|
12
|
+
return result
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def self.extend(base)
|
18
|
+
base.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
def first_midnight
|
22
|
+
return DateTime.new(0, 1, 1, 0, 0, 0)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mo2tex
|
4
|
+
|
5
|
+
class SlotBase
|
6
|
+
|
7
|
+
include DateTimeHelper
|
8
|
+
|
9
|
+
attr_reader :day
|
10
|
+
attr_accessor :dtstart, :dtend
|
11
|
+
|
12
|
+
def initialize(day, ts, te)
|
13
|
+
@day = day
|
14
|
+
self.dtstart = ts
|
15
|
+
self.dtend = te
|
16
|
+
end
|
17
|
+
|
18
|
+
def dtstart=(ts)
|
19
|
+
@dtstart = common_equal(ts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def dtend=(te)
|
23
|
+
@dtend = common_equal(te)
|
24
|
+
end
|
25
|
+
|
26
|
+
def color
|
27
|
+
raise PureVirtualMethodCalled, "#{self.class}#color"
|
28
|
+
end
|
29
|
+
|
30
|
+
def title
|
31
|
+
raise PureVirtualMethodCalled, "#{self.class}#title"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def common_equal(t)
|
37
|
+
result = t if t.is_a?(DateTime)
|
38
|
+
result = datetime_create(self.day, t) if t.is_a?(String)
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class FreeSlot < SlotBase
|
45
|
+
|
46
|
+
def title
|
47
|
+
return "FREE"
|
48
|
+
end
|
49
|
+
|
50
|
+
def color
|
51
|
+
return "\"red\""
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class LunchBreak < SlotBase
|
57
|
+
|
58
|
+
def title
|
59
|
+
return "Pranzo"
|
60
|
+
end
|
61
|
+
|
62
|
+
def color
|
63
|
+
return "\"grey50\""
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class DaySchedule
|
69
|
+
|
70
|
+
include DateTimeHelper
|
71
|
+
|
72
|
+
attr_reader :day, :tstart, :tend, :lbtstart, :lbtend, :dtstart, :dtend, :lbdtstart, :lbdtend, :lnumber, :wday
|
73
|
+
attr_reader :slots
|
74
|
+
attr_accessor :dirty
|
75
|
+
|
76
|
+
def initialize(day, ln, wd, conf)
|
77
|
+
raise DayIsNotDateTime, "#{day}" unless day.is_a?(DateTime)
|
78
|
+
raise ArgumentError, "#{conf}" unless conf.is_a?(Hash)
|
79
|
+
@day = day # should be a DateTime with the d/m/y
|
80
|
+
@wday = wd
|
81
|
+
@tstart = conf[self.wday]['orario'][0]
|
82
|
+
@tend = conf[self.wday]['orario'][1]
|
83
|
+
@lbtstart = conf[self.wday]['pausa'][0]
|
84
|
+
@lbtend = conf[self.wday]['pausa'][1]
|
85
|
+
@dtstart = datetime_create(self.day, self.tstart)
|
86
|
+
@dtend = datetime_create(self.day, self.tend)
|
87
|
+
@lbdtstart = datetime_create(self.day, self.lbtstart)
|
88
|
+
@lbdtend = datetime_create(self.day, self.lbtend)
|
89
|
+
@lnumber = ln
|
90
|
+
@slots = []
|
91
|
+
self.dirty = false
|
92
|
+
calculate_free_slots
|
93
|
+
end
|
94
|
+
|
95
|
+
def dirty?
|
96
|
+
return self.dirty
|
97
|
+
end
|
98
|
+
|
99
|
+
def <<(event)
|
100
|
+
return unless this_slot?(event)
|
101
|
+
self.slots << event
|
102
|
+
self.dirty = true
|
103
|
+
end
|
104
|
+
|
105
|
+
def calculate_free_slots
|
106
|
+
if self.slots.empty?
|
107
|
+
self.slots << LunchBreak.new(self.day, self.lbdtstart, self.lbdtend)
|
108
|
+
else
|
109
|
+
bsorted = self.slots.sort { |a, b| a.dtstart <=> b.dtstart }
|
110
|
+
tstart = self.dtstart
|
111
|
+
bsorted.each do
|
112
|
+
|ev|
|
113
|
+
self.slots << FreeSlot.new(self.day, tstart, ev.dtstart) if (ev.dtstart.round_to_ymdhm > tstart.round_to_ymdhm)
|
114
|
+
tstart = ev.dtend
|
115
|
+
end
|
116
|
+
self.slots << FreeSlot.new(self.day, tstart, self.dtend) if (tstart.round_to_ymdhm < self.dtend.round_to_ymdhm)
|
117
|
+
end
|
118
|
+
self.slots.sort! { |a, b| a.dtstart <=> b.dtstart }
|
119
|
+
self.dirty = false
|
120
|
+
return self.slots
|
121
|
+
end
|
122
|
+
|
123
|
+
def each_slot(&block)
|
124
|
+
self.calculate_free_slots if self.dirty?
|
125
|
+
self.slots.sort! { |a, b| a.dtstart <=> b.dtstart }
|
126
|
+
self.slots.each { |slot| yield(slot) } if block_given?
|
127
|
+
end
|
128
|
+
|
129
|
+
def this_slot?(ev)
|
130
|
+
return (ev.dtstart.day_only >= self.dtstart.day_only && ev.dtend.day_only <= self.dtend.day_only)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
data/lib/mo2tex/event.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'icalendar'
|
4
|
+
|
5
|
+
module Mo2tex
|
6
|
+
|
7
|
+
class Event
|
8
|
+
|
9
|
+
attr_reader :dtstart, :dtend, :lnumber, :ltot, :title, :desc, :loc, :online
|
10
|
+
|
11
|
+
def initialize(ls, le, ln, lt, t, desc, loc, online)
|
12
|
+
@dtstart = DateTime.parse(ls.to_s)
|
13
|
+
@dtend = DateTime.parse(le.to_s)
|
14
|
+
@lnumber = ln
|
15
|
+
@ltot = lt
|
16
|
+
@title = t
|
17
|
+
@desc = desc
|
18
|
+
@loc = loc
|
19
|
+
@online = online
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
return self.title + ' ' + self.dtstart.strftime("%d/%m/%Y %H:%M") + '-' + self.dtend.strftime("%H:%M") + ' ' + self.lnumber.to_s + '/' + self.ltot.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_ical(ical)
|
27
|
+
ical.event do
|
28
|
+
|e|
|
29
|
+
e.dtstart = self.dtstart
|
30
|
+
e.dtend = self.dtend
|
31
|
+
e.summary = self.title
|
32
|
+
e.description = self.desc
|
33
|
+
e.location = self.loc
|
34
|
+
e.ip_class = 'PUBLIC'
|
35
|
+
if is_online?
|
36
|
+
e.location = 'Microsoft Teams Meeting'
|
37
|
+
e.description += <<EOR
|
38
|
+
\n_____________________________________________________________
|
39
|
+
___________________\nMicrosoft Teams Need help?<https://aka.ms/JoinTeamsMe
|
40
|
+
eting?omkt=en-US>\nJoin the meeting now<https://teams.microsoft.com/l/meet
|
41
|
+
up-join/19%3ameeting_MWZlZDdkZmMtNjRkNi00M2U3LTgwMzUtMjkwNGQ1YTY3N2I4%40th
|
42
|
+
read.v2/0?context=%7b%22Tid%22%3a%2276c57fd2-8d47-4cb5-a7a2-83c2cd0c3565%2
|
43
|
+
2%2c%22Oid%22%3a%22a29bd7f0-b99d-4031-8cf5-fabe578a7d05%22%7d>\nMeeting ID
|
44
|
+
: 358 935 834 932\nPasscode: 9wNLge\n________________________________\nFor
|
45
|
+
organizers: Meeting options<https://teams.microsoft.com/meetingOptions/?o
|
46
|
+
rganizerId=a29bd7f0-b99d-4031-8cf5-fabe578a7d05&tenantId=76c57fd2-8d47-4cb
|
47
|
+
5-a7a2-83c2cd0c3565&threadId=19_meeting_MWZlZDdkZmMtNjRkNi00M2U3LTgwMzUtMj
|
48
|
+
kwNGQ1YTY3N2I4@thread.v2&messageId=0&language=en-US>\n____________________
|
49
|
+
____________________________________________________________\n
|
50
|
+
EOR
|
51
|
+
end
|
52
|
+
end
|
53
|
+
return ical
|
54
|
+
end
|
55
|
+
|
56
|
+
def color
|
57
|
+
return "\"green\""
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def is_online?
|
63
|
+
result = false
|
64
|
+
self.online.each do
|
65
|
+
|d|
|
66
|
+
dt = DateTime.parse(d.to_s)
|
67
|
+
if self.dtstart.year == dt.year && self.dtstart.month == dt.month && self.dtstart.day == dt.day
|
68
|
+
result = true
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
return result
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
|
4
|
+
module Mo2tex
|
5
|
+
|
6
|
+
class UnknownCourse < Error; end
|
7
|
+
class UnknownCourseType < Error; end
|
8
|
+
class UnknownRepetition < Error; end
|
9
|
+
class PureVirtuaMethodCalled < Error; end
|
10
|
+
class DateOutOfRange < Error; end
|
11
|
+
class UnexistentWeekDay < Error; end
|
12
|
+
class UnknownCourseFrequency < Error; end
|
13
|
+
class WrongWeekDay < Error; end
|
14
|
+
class DayIsNotDateTime < Error; end
|
15
|
+
class TimeIsNotString < Error; end
|
16
|
+
class NotAPicContainer < Error; end
|
17
|
+
class GenerationError < Error; end
|
18
|
+
class NotEnoughLessons < GenerationError; end
|
19
|
+
class EventsNotGenerated < GenerationError; end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Mo2tex
|
6
|
+
|
7
|
+
class ExcludeBase
|
8
|
+
|
9
|
+
attr_reader :start_period, :end_period
|
10
|
+
|
11
|
+
def initialize(sp, ep)
|
12
|
+
@start_period = DateTime.parse(sp.to_s)
|
13
|
+
@end_period = DateTime.parse(ep.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def skip?(d)
|
17
|
+
dt = DateTime.parse(d.to_s)
|
18
|
+
return true unless within_period?(dt)
|
19
|
+
return does_it_skip?(dt)
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def does_it_skip?(d)
|
25
|
+
raise PureVirtualMethodCalled, 'ExcludeBase#skip'
|
26
|
+
end
|
27
|
+
|
28
|
+
def within_period?(d)
|
29
|
+
return (d >= self.start_period && d <= self.end_period)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class ExcludeDate < ExcludeBase
|
35
|
+
|
36
|
+
attr_reader :date
|
37
|
+
|
38
|
+
def initialize(d, sd, ed)
|
39
|
+
super(sd, ed)
|
40
|
+
@date = DateTime.parse(d.to_s)
|
41
|
+
raise DateOutOfRange, self.date.to_s unless within_period?(self.date)
|
42
|
+
end
|
43
|
+
|
44
|
+
def does_it_skip?(d)
|
45
|
+
return d == self.date
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class ExcludeDates < ExcludeBase
|
51
|
+
|
52
|
+
attr_reader :start_date, :end_date
|
53
|
+
|
54
|
+
def initialize(sd, ed, sp, ep)
|
55
|
+
super(sp, ep)
|
56
|
+
@start_date = DateTime.parse(sd.to_s)
|
57
|
+
@end_date = DateTime.parse(ed.to_s)
|
58
|
+
raise DateOutOfRange, self.start_date.to_s unless within_period?(self.start_date)
|
59
|
+
raise DateOutOfRange, self.end_date.to_s unless within_period?(self.end_date)
|
60
|
+
end
|
61
|
+
|
62
|
+
def does_it_skip?(d)
|
63
|
+
return (d >= self.start_date && d <= self.end_date)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Mo2tex
|
6
|
+
|
7
|
+
class ExclusionManager
|
8
|
+
|
9
|
+
attr_reader :excluded, :start_period, :end_period
|
10
|
+
|
11
|
+
def initialize(exl_array, sp, ep)
|
12
|
+
@start_period = DateTime.parse(sp.to_s)
|
13
|
+
@end_period = DateTime.parse(ep.to_s)
|
14
|
+
@excluded = parse(exl_array)
|
15
|
+
end
|
16
|
+
|
17
|
+
def skip?(d)
|
18
|
+
result = false
|
19
|
+
self.excluded.each { |ed| result |= ed.skip?(d) }
|
20
|
+
return result
|
21
|
+
end
|
22
|
+
|
23
|
+
def out_of_range?(d)
|
24
|
+
dt = DateTime.parse(d.to_s)
|
25
|
+
return (dt < self.start_period || dt > self.end_period)
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
return self.excluded.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def <<(dts)
|
33
|
+
arg = dts.is_a?(Array) ? dts : [ dts ]
|
34
|
+
result = parse(arg)
|
35
|
+
self.excluded.concat(result)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def parse(excl_array)
|
41
|
+
result = []
|
42
|
+
excl_array.each do
|
43
|
+
|el|
|
44
|
+
case el
|
45
|
+
when String
|
46
|
+
(sd, ed) = el.split(/-/).map { |n| n.to_i }
|
47
|
+
result << ExcludeDates.new(sd, ed, self.start_period, self.end_period)
|
48
|
+
when Integer
|
49
|
+
result << ExcludeDate.new(el, self.start_period, self.end_period)
|
50
|
+
else
|
51
|
+
raise ArgumentError, "#{el} not recognized"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return result
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
class DateTime
|
6
|
+
|
7
|
+
def hm_to_s
|
8
|
+
result = sprintf("%02d:%02d", self.hour, self.minute)
|
9
|
+
return result
|
10
|
+
end
|
11
|
+
|
12
|
+
ITWDAYS = [ 'domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato', ]
|
13
|
+
|
14
|
+
def to_it_wday
|
15
|
+
result = ITWDAYS[self.wday]
|
16
|
+
raise UnexistentWeekDay, wd.to_s unless result
|
17
|
+
return result
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_it_roff_wday
|
21
|
+
return to_it_wday.sub(/ì/, "\\(`i")
|
22
|
+
end
|
23
|
+
|
24
|
+
def day_only
|
25
|
+
return self.to_date.to_datetime
|
26
|
+
end
|
27
|
+
|
28
|
+
def round_to_ymdhm
|
29
|
+
result = DateTime.new(self.year, self.month, self.day, self.hour, self.min, 0, 0)
|
30
|
+
return result
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
FIRST_MIDNIGHT = DateTime.new(0, 1, 1, 0, 0, 0, 0)
|
36
|
+
|
37
|
+
def first_midnight
|
38
|
+
return FIRST_MIDNIGHT
|
39
|
+
end
|
40
|
+
|
41
|
+
def dayfrac_to_dt(frac)
|
42
|
+
return first_midnight + frac.to_r
|
43
|
+
end
|
44
|
+
|
45
|
+
def dayfrac_to_hm(frac)
|
46
|
+
dt = dayfrac_to_dt(frac)
|
47
|
+
return [dt.hour, dt.minute]
|
48
|
+
end
|
49
|
+
|
50
|
+
def dayfrac_to_hms(frac)
|
51
|
+
return dayfrac_to_dt(frac).hm_to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def dayfrac_to_dur(frac)
|
55
|
+
return ((frac.to_r*24)*10).round/10.0
|
56
|
+
end
|
57
|
+
|
58
|
+
def from_it_wday(wday)
|
59
|
+
result = ITWDAYS.index(wday)
|
60
|
+
raise UnexistentWeekDay, wday unless result
|
61
|
+
return result
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/mo2tex/latex.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module Mo2tex
|
6
|
+
|
7
|
+
DEFAULT_LTDIR = File.expand_path(File.join(['..']*3, 'config', 'templates', 'latex'), __FILE__)
|
8
|
+
|
9
|
+
class Latex
|
10
|
+
|
11
|
+
attr_reader :school, :header, :trailer, :body, :total_hours_done
|
12
|
+
|
13
|
+
def initialize(s)
|
14
|
+
@total_hours_done = 0
|
15
|
+
@school = s
|
16
|
+
@header = read_header
|
17
|
+
@trailer = read_trailer
|
18
|
+
@body = read_body
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate
|
22
|
+
result = self.header
|
23
|
+
result += self.body.result(binding)
|
24
|
+
result += self.trailer
|
25
|
+
end
|
26
|
+
|
27
|
+
def table_body
|
28
|
+
result = ''
|
29
|
+
head = proc { |title| "\\hline\n\\multicolumn{3}{|l|}{\\bfseries #{title}}\\\\\n\\hline\n" }
|
30
|
+
tail = proc { |sctot| "\\multicolumn{2}{l}{Totale ore corso:} & \\hfill#{sctot.round.to_i}\\\\\n & & \\\\\n" }
|
31
|
+
@total_hours_done, result = self.school.generate(head, tail) do
|
32
|
+
|title, nol, ev|
|
33
|
+
sprintf("%s & %s--%s & Lezione n.%02d/%02d\\\\\n", ev.dtstart.strftime("%d/%m/%Y"),
|
34
|
+
ev.dtstart.strftime("%H:%M"),
|
35
|
+
ev.dtend.strftime("%H:%M"),
|
36
|
+
ev.lnumber, ev.ltot)
|
37
|
+
end
|
38
|
+
@total_hours_done = @total_hours_done.round.to_i
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def read_header
|
45
|
+
return read('header.tex')
|
46
|
+
end
|
47
|
+
|
48
|
+
def read_trailer
|
49
|
+
return read('trailer.tex')
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_body
|
53
|
+
template = read('body.tex.erb')
|
54
|
+
return ERB.new(template)
|
55
|
+
end
|
56
|
+
|
57
|
+
def read(what)
|
58
|
+
result = nil
|
59
|
+
tdir ||= DEFAULT_LTDIR
|
60
|
+
File.open(File.join(tdir, what)) { |fh| result = fh.readlines.join }
|
61
|
+
return result
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/mo2tex/main.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Mo2tex
|
6
|
+
|
7
|
+
module CommonParts
|
8
|
+
|
9
|
+
def m2common(args, io = STDOUT, &block)
|
10
|
+
raise ArgumentError, "Usage: #{File.basename($0)} <config file>" unless args.size > 0
|
11
|
+
school = School.new(args[0])
|
12
|
+
result = yield(school)
|
13
|
+
io.puts(result)
|
14
|
+
return result
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
include CommonParts
|
22
|
+
|
23
|
+
#
|
24
|
+
# +m2latex+: produces a LaTeX file from a configuration
|
25
|
+
#
|
26
|
+
def m2latex(args, io = STDOUT)
|
27
|
+
text = m2common(args, io) do
|
28
|
+
|school|
|
29
|
+
latex = Latex.new(school)
|
30
|
+
return latex.generate
|
31
|
+
end
|
32
|
+
return text
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# +m2ical+: produces an iCal file from a configuration
|
37
|
+
#
|
38
|
+
def m2ical(args, io = STDOUT)
|
39
|
+
ical = m2common(args, io) do
|
40
|
+
|school|
|
41
|
+
ical = school.cal.create_icalendar(school)
|
42
|
+
return ical.to_ical
|
43
|
+
end
|
44
|
+
return ical
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# +m2pic+: produces a pic file from a configuration
|
49
|
+
#
|
50
|
+
def m2pic(args, io = STDOUT)
|
51
|
+
text = m2common(args, io) do
|
52
|
+
|school|
|
53
|
+
pic = Pic::Writer.new(school)
|
54
|
+
return pic.generate
|
55
|
+
end
|
56
|
+
return text
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|