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.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # `mo2tex` - A pure `ruby` monteore-to-`latex` table generator
2
+
3
+ ![[pipeline](https://gitlab.com/SMELAT/mo2tex)](https://gitlab.com/SMELAT/mo2tex/badges/master/pipeline.svg)
4
+
5
+ `mo2tex` is a pure `ruby` monteore-to-`latex` table generator
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add mo2tex
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install mo2tex
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
+
25
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
26
+
27
+ ## Contributing
28
+
29
+ Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/SMELAT/mo2tex.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[spec]
@@ -0,0 +1,165 @@
1
+ calendar:
2
+ start_date: 20241104
3
+ end_date: 20251031
4
+ exclude: [ 20241221-20250106, 20250220-20250301, 20250417-20250422, 20250425, 20250501, 20250602, 20250625-20251011 ]
5
+ file: Bernardini-A.A.2024-2025.ics
6
+ day_schedule:
7
+ lunedì:
8
+ orario:
9
+ - '9:00'
10
+ - '15:00'
11
+ pausa:
12
+ - '13:30'
13
+ - '14:00'
14
+ martedì:
15
+ orario:
16
+ - '9:00'
17
+ - '17:00'
18
+ pausa:
19
+ - '12:00'
20
+ - '13:00'
21
+
22
+ courses:
23
+ Bernardini_-_Composizione_Musicale_Elettroacustica_TR_I:
24
+ studenti: Timpone, Valle
25
+ ore: 30
26
+ tipologia: individuale
27
+ tip_effettiva: individuale
28
+ inizio: 20241104
29
+ frequenza: settimanale
30
+ giorno: lunedì
31
+ orario: 9:00
32
+ durata_lezione: 1:30
33
+ aula: 18s
34
+ online: [ 20241125 ]
35
+
36
+ Bernardini_-_Composizione_Musicale_Elettroacustica_TR_II:
37
+ studenti: Freddoneve, De Paolis
38
+ ore: 30
39
+ tipologia: individuale
40
+ tip_effettiva: individuale
41
+ inizio: 20241104
42
+ frequenza: settimanale
43
+ giorno: lunedì
44
+ orario: 10:30
45
+ durata_lezione: 1:30
46
+ aula: 18s
47
+ online: [ 20241125 ]
48
+
49
+ Bernardini_-_Composizione_Musicale_Elettroacustica_TR_III_A:
50
+ studenti: Diaz, Erdi
51
+ studenti_fc: Corsi
52
+ ore: 30
53
+ tipologia: individuale
54
+ tip_effettiva: individuale
55
+ inizio: 20241104
56
+ frequenza: settimanale
57
+ giorno: lunedì
58
+ orario: 12:00
59
+ durata_lezione: 1:30
60
+ aula: 18s
61
+ online: [ 20241125 ]
62
+
63
+ Bernardini_-_Composizione_Musicale_Elettroacustica_TR_III_B:
64
+ studenti: Pellegrino, Cutini, De Caro
65
+ ore: 30
66
+ tipologia: individuale
67
+ tip_effettiva: collettiva
68
+ inizio: 20241112
69
+ frequenza: quindicinale
70
+ giorno: martedì
71
+ orario: 15:00
72
+ durata_lezione: 2:00
73
+ aula: ottofonia
74
+ online: [ 20241126 ]
75
+
76
+ Bernardini_-_Composizione_Musicale_Informatica_TR_I_TS:
77
+ studenti: Noto, Varone, Mauti, Savastano, Vona, Cannuciari
78
+ ore: 20
79
+ tipologia: collettiva
80
+ tip_effettiva: collettiva
81
+ inizio: 20241104
82
+ frequenza: settimanale
83
+ giorno: lunedì
84
+ orario: 14:00
85
+ durata_lezione: 1:30
86
+ aula: 18s
87
+ online: [ 20241125 ]
88
+
89
+ Bernardini_-_Composizione_Musicale_Informatica_TR_II_TS:
90
+ studenti: Chierici, Rossi
91
+ ore: 20
92
+ tipologia: collettiva
93
+ tip_effettiva: collettiva
94
+ inizio: 20241105
95
+ frequenza: settimanale
96
+ giorno: martedì
97
+ orario: 9:00
98
+ durata_lezione: 1:00
99
+ aula: ottofonia
100
+ online: [ 20241126 ]
101
+
102
+ Bernardini_-_Composizione_Audiovisiva_Integrata_TR_I_A_MA:
103
+ studenti: Di Girolamo, Bonfè, Bongiovanni
104
+ ore: 30
105
+ tipologia: individuale
106
+ tip_effettiva: collettiva
107
+ inizio: 20241105
108
+ frequenza: settimanale
109
+ giorno: martedì
110
+ orario: 13:00
111
+ durata_lezione: 2:00
112
+ aula: ottofonia
113
+ online: [ 20241126 ]
114
+
115
+ Bernardini_-_Composizione_Audiovisiva_Integrata_TR_I_B_MA:
116
+ studenti: Becherucci, Filippi, Barbarossa
117
+ ore: 30
118
+ tipologia: individuale
119
+ tip_effettiva: collettiva
120
+ inizio: 20250318
121
+ frequenza: settimanale
122
+ giorno: martedì
123
+ orario: 13:00
124
+ durata_lezione: 2:00
125
+ aula: ottofonia
126
+ online: [ 20241126 ]
127
+
128
+ Bernardini_-_Composizione_Musicale_Elettroacustica_BN_II_ME:
129
+ studenti: Ferracuti
130
+ ore: 20
131
+ tipologia: individuale
132
+ tip_effettiva: individuale
133
+ inizio: 20241105
134
+ frequenza: settimanale
135
+ giorno: martedì
136
+ orario: 10:00
137
+ durata_lezione: 1:00
138
+ aula: ottofonia
139
+ online: [ 20241126 ]
140
+
141
+ Bernardini_-_Composizione_Audiovisiva_Integrata_BN_II_ME:
142
+ studenti: Ferracuti
143
+ ore: 20
144
+ tipologia: individuale
145
+ tip_effettiva: individuale
146
+ inizio: 20250318
147
+ frequenza: settimanale
148
+ giorno: martedì
149
+ orario: 10:00
150
+ durata_lezione: 1:00
151
+ aula: ottofonia
152
+ online: [ 20241126 ]
153
+
154
+ Bernardini_-_Composizione_Audiovisiva_Integrata_BN_I_MA:
155
+ studenti: Rossi Minelli
156
+ ore: 20
157
+ tipologia: individuale
158
+ tip_effettiva: individuale
159
+ inizio: 20241105
160
+ frequenza: settimanale
161
+ giorno: martedì
162
+ orario: 11:00
163
+ durata_lezione: 1:00
164
+ aula: ottofonia
165
+ online: [ 20241126 ]
@@ -0,0 +1,5 @@
1
+ \begin{longtable}{l c r}
2
+ <%= self.table_body %>
3
+ \hline
4
+ \multicolumn{2}{l}{\bfseries Totale ore:} & {\bfseries\large\hfill <%= self.total_hours_done %>}\\
5
+ \end{longtable}
@@ -0,0 +1,20 @@
1
+ \documentclass[10pt]{scrartcl}
2
+ \usepackage[italian]{babel}
3
+ \usepackage[margin=1cm]{geometry}
4
+ \usepackage{longtable}
5
+ \usepackage{needspace}
6
+ \usepackage{xspace}
7
+
8
+ \newcommand{\AAcc}{A.A. 2024--2025\xspace}
9
+
10
+ \title{Conservatorio ``Ottorino Respighi'' Latina\\%
11
+ Scuola di Musica Elettronica\\%
12
+ \AAcc\\%
13
+ Docente: Nicola Bernardini\\%
14
+ Orari delle lezioni}
15
+
16
+ \date{~}
17
+
18
+ \begin{document}
19
+
20
+ \maketitle
@@ -0,0 +1 @@
1
+ \end{document}
@@ -0,0 +1,14 @@
1
+ .PS
2
+ # -*- coding: utf-8 -*-
3
+
4
+ #
5
+ # Page n.<%= page.number %>
6
+ #
7
+ page_frame(<%= page.number -%>)
8
+ <% page.containers.each do |cont| -%>
9
+ container(<%= cont.number %>, <%= cont.location_tag %>, "\s[-2]\fB<%= cont.wdate %>\fP\s0")
10
+ <% cont.slots.each do |ev| -%>
11
+ event(<%= ev.number %>, <%= ev.location_tag %>, "\s[-3]<%= ev.pic_time %>\s0", <%= ev.pic_dur %>, "\s[-2]\fB<%= ev.pic_title %>\fP\s0", <%= ev.color %>)
12
+ <% end %>
13
+ <% end %>
14
+ .PE
@@ -0,0 +1,13 @@
1
+ .po 0.5i
2
+ .ll 7.27i
3
+ .\".pl 10.423
4
+ .PS
5
+ scale=2.54 # we want centimeters
6
+ maxpswid=21 # a4 portrait with 0cm margin
7
+ maxpsht=29.7
8
+ textwid=maxpswid-scale
9
+ textht =maxpsht-1.035*scale
10
+
11
+ copy "config/templates/pic/objects.pic"
12
+ .PE
13
+
@@ -0,0 +1,46 @@
1
+ .PS
2
+ #
3
+ # page frame:
4
+ #
5
+ # +page_frame(npage)+
6
+ #
7
+ # creates a page where to insert containers
8
+ #
9
+ framewid = textwid
10
+ frameht = textht
11
+ define page_frame {
12
+ PageFrame$1: box invis wid framewid ht frameht
13
+ }
14
+ #
15
+ # main container:
16
+ #
17
+ # +container(container_number, placement, daystring)+
18
+ #
19
+ # will produce a Container<container_number> box where to put each day
20
+ #
21
+ containerht = frameht/4
22
+ containerwid = framewid/4
23
+ subcontainerwid = containerwid*0.65
24
+ subcontainerht = containerht*0.93
25
+ define container {
26
+ Container$1: box wid containerwid ht containerht with .nw at $2
27
+ Subcontainer$1: box wid subcontainerwid ht subcontainerht with .se at Container$1.se
28
+ line invis from Container$1.nw to Container$1.ne $3 below
29
+ }
30
+ #
31
+ # event:
32
+ #
33
+ # +event(event_number, placement, time, dur, title, color)+
34
+ #
35
+ # will produce a Event<event_number> box where to put the weeks
36
+ #
37
+ totdaydur = 8
38
+ dayoffset = 9
39
+ define event {
40
+ event_ht = (subcontainerht/totdaydur)*$4
41
+ Event$1: box wid subcontainerwid ht event_ht with .nw at $2 $5 shaded $6
42
+ tag_wid = containerwid - subcontainerwid
43
+ Ttag$1: box invis wid tag_wid ht event_ht with .e at Event$1.w
44
+ line invis from Ttag$1.nw to Ttag$1.ne $3 below
45
+ }
46
+ .PE
@@ -0,0 +1,3 @@
1
+ .\"
2
+ .\" created with mo2tex rev.<%= Git::Describe::git %>
3
+ .\"
data/exe/m2c ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ version = '1.0.0'
4
+ ourlib = File.join(ENV['HOME'], '.rvm', 'gems', 'ruby-3.4.0-preview1@mo2tex', 'gems', "mo2tex-#{version}", 'lib')
5
+ $:<< File.join('.', 'lib')
6
+ $:<< ourlib
7
+ require 'mo2tex'
8
+
9
+ begin
10
+ puts(Mo2tex::m2ical(ARGV))
11
+ rescue ArgumentError => msg
12
+ STDERR.puts(msg)
13
+ exit(-1)
14
+ end
15
+
16
+ exit(0)
data/exe/m2l ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ version = '1.0.0'
4
+ ourlib = File.join(ENV['HOME'], '.rvm', 'gems', 'ruby-3.4.0-preview1@mo2tex', 'gems', "mo2tex-#{version}", 'lib')
5
+ $:<< File.join('.', 'lib')
6
+ $:<< ourlib
7
+ require 'mo2tex'
8
+
9
+ begin
10
+ puts(Mo2tex::m2latex(ARGV))
11
+ rescue ArgumentError => msg
12
+ STDERR.puts(msg)
13
+ exit(-1)
14
+ end
15
+
16
+ exit(0)
data/exe/m2p ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ version = '1.0.0'
4
+ ourlib = File.join(ENV['HOME'], '.rvm', 'gems', 'ruby-3.4.0-preview1@mo2tex', 'gems', "mo2tex-#{version}", 'lib')
5
+ $:<< File.join('.', 'lib')
6
+ $:<< ourlib
7
+ require 'mo2tex'
8
+
9
+ begin
10
+ puts(Mo2tex::m2pic(ARGV))
11
+ rescue ArgumentError => msg
12
+ STDERR.puts(msg)
13
+ exit(-1)
14
+ end
15
+
16
+ exit(0)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'date'
5
+ require 'icalendar'
6
+
7
+ module Mo2tex
8
+
9
+ class Calendar
10
+
11
+ attr_reader :filename, :start_date, :end_date, :exclude, :events, :day_schedule_templates
12
+
13
+ class UnknownEvent < Error; end
14
+
15
+ def initialize(config)
16
+ @filename = config['file']
17
+ @start_date = DateTime.parse(config['start_date'].to_s)
18
+ @end_date = DateTime.parse(config['end_date'].to_s)
19
+ @exclude = ExclusionManager.new(config['exclude'], self.start_date, self.end_date)
20
+ @day_schedule_templates = parse_day_schedule_templates(config['day_schedule'])
21
+ end
22
+
23
+ def create_icalendar(school)
24
+ ical = Icalendar::Calendar.new
25
+ school.courses.each do
26
+ |title, course|
27
+ course.events.each do
28
+ |ev|
29
+ ev.to_ical(ical)
30
+ end
31
+ end
32
+ return ical.publish
33
+ end
34
+
35
+ def create_pic_schedule(school)
36
+ pic = Pic.new(school)
37
+ end
38
+
39
+ def to_file(school, fname = nil)
40
+ fname ||= self.filename
41
+ ical = self.create_icalendar(school)
42
+ File.open(fname, 'w') { |fh| fh.puts(ical.to_ical) }
43
+ return fname
44
+ end
45
+
46
+ private
47
+
48
+ def parse_day_schedule_templates(conf)
49
+ return conf
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Mo2tex
6
+
7
+ class Course
8
+
9
+ include CourseHelper
10
+
11
+ attr :title, :hours, :type, :real_type, :students, :students_fc,
12
+ :start, :freq, :wday, :dur, :when, :events, :excl_mgr, :warnings,
13
+ :hours_done, :where, :online
14
+
15
+ def initialize(title, desc, exmgr)
16
+ @warnings = {}
17
+ @hours_done = 0
18
+ @excl_mgr = exmgr
19
+ self.excl_mgr << desc['exclude'] if desc.has_key?('exclude')
20
+ @title = normalize_title(title)
21
+ @hours = desc['ore']
22
+ @type = desc['tipologia']
23
+ @real_type = desc['tip_effettiva']
24
+ @students = desc['studenti']
25
+ @students_fc = desc['studenti_fc']
26
+ @start = DateTime.parse(desc['inizio'].to_s)
27
+ @freq = parse_frequency(desc['frequenza'])
28
+ @wday = desc['giorno']
29
+ @dur = parse_dur(desc['durata_lezione']) # beware! YAML converts in seconds!
30
+ @when = parse_dur(desc['orario']) # beware! YAML converts in seconds!
31
+ @where = 'aula ' + desc['aula']
32
+ @online = desc['online']
33
+ @events = generate_events
34
+ end
35
+
36
+ def generate(&block)
37
+ result = ''
38
+ self.events.each do
39
+ |ev|
40
+ result += yield(self.title, self.number_of_lessons, ev)
41
+ end
42
+ return result
43
+ end
44
+
45
+ def course_dur_in_seconds
46
+ return self.academic_hours * 3600
47
+ end
48
+
49
+ def lesson_dur_in_seconds
50
+ return ((self.dur * 24)*3600).round.to_i
51
+ end
52
+
53
+ def number_of_lessons
54
+ result = (self.course_dur_in_seconds.to_f / self.lesson_dur_in_seconds.to_f).round.to_i
55
+ return result
56
+ end
57
+
58
+ class <<self
59
+
60
+ def create(title, desc, exm)
61
+ result = nil
62
+ case desc['tip_effettiva']
63
+ when 'collettiva'
64
+ result = CollectiveCourse.new(title, desc, exm)
65
+ when 'individuale'
66
+ result = IndividualCourse.new(title, desc, exm)
67
+ else
68
+ raise UnknownCourseType, "Unknown type #{desc['tipologia']}"
69
+ end
70
+ return result
71
+ end
72
+
73
+ end
74
+
75
+ def num_students
76
+ split_studs = self.students.split(/, */)
77
+ return split_studs.size
78
+ end
79
+
80
+ def total_hours
81
+ raise PureVirtualMethodCalled, 'Course#total_hours'
82
+ end
83
+
84
+ def academic_hours
85
+ return self.total_hours
86
+ end
87
+
88
+ def all_students
89
+ return self.students + ' ' + self.students_fc
90
+ end
91
+
92
+ def hours_done_per_lesson(dts, dte)
93
+ raise PureVirtualMethodCalled, 'Course#hours_done_per_lesson'
94
+ end
95
+
96
+ private
97
+
98
+ def latex_title
99
+ return "\\multicolumn{3}{\\bfseries #{self.title}}\\\\\n"
100
+ end
101
+
102
+ def parse_frequency(f)
103
+ result = nil
104
+ case f
105
+ when 'settimanale'
106
+ result = 7
107
+ when 'quindicinale'
108
+ result = 14
109
+ else
110
+ raise UnknownCourseFrequency, f
111
+ end
112
+ return result
113
+ end
114
+
115
+ #
116
+ # +parse_dur(d)+
117
+ #
118
+ # the argument is a number converted in seconds (usually, directly by YAML)
119
+ # so, in order to get it palatable to DateTime, we reconvert it in fraction of days.
120
+ #
121
+ def parse_dur(d)
122
+ frac_hours = d.to_f/3600.0
123
+ result = (frac_hours/24.0).to_r
124
+ return result
125
+ end
126
+
127
+ def generate_events
128
+ result = []
129
+ tot_nlessons = self.number_of_lessons
130
+ current_date = self.start
131
+ nless = 1
132
+ while (nless <= tot_nlessons) do
133
+ if self.excl_mgr.skip?(current_date)
134
+ current_date += self.freq
135
+ break if self.excl_mgr.out_of_range?(current_date)
136
+ next
137
+ end
138
+ lesson_start = calc_lesson_start(current_date)
139
+ lesson_end = calc_lesson_end(current_date)
140
+ result << Event.new(lesson_start, lesson_end, nless, tot_nlessons, self.title, self.students, self.where, self.online)
141
+ current_date += self.freq
142
+ raise WrongWeekDay, "#{current_date.to_it_wday} != #{self.wday} for course \"#{self.title}\"" unless current_date.wday == DateTime.from_it_wday(self.wday)
143
+ nless += 1
144
+ @hours_done += self.hours_done_per_lesson(lesson_start, lesson_end) # hours spent teaching
145
+ end
146
+ raise EventsNotGenerated, "no events generated for course \"#{self.title}\" (#{self.type})" unless result && !result.empty?
147
+ self. warnings[:not_enough_lessons] = "n.lessons(#{result.size}) != expected number of lessons(#{self.number_of_lessons}) for course \"#{self.title}\" (#{self.type})" unless result.size == self.number_of_lessons
148
+ return result
149
+ end
150
+
151
+ def calc_lesson_start(dt)
152
+ return calc_time(dt, mceil(self.when))
153
+ end
154
+
155
+ def calc_lesson_end(dt)
156
+ return calc_time(dt, mceil(self.when) + mceil(self.dur))
157
+ end
158
+
159
+ def calc_time(dt, t)
160
+ return dt + t
161
+ end
162
+
163
+ def mceil(n, prec = 10000)
164
+ return ((n.to_f*prec.to_f).ceil/prec.to_f)
165
+ end
166
+
167
+ end
168
+
169
+ class CollectiveCourse < Course
170
+
171
+ def total_hours
172
+ return self.hours
173
+ end
174
+
175
+ def hours_done_per_lesson(dts, dte)
176
+ return (dte - dts).to_f * 24.0
177
+ end
178
+
179
+ end
180
+
181
+ class IndividualCourse < Course
182
+
183
+ def total_hours
184
+ return self.hours * self.num_students
185
+ end
186
+
187
+ def academic_hours
188
+ return (self.total_hours * 0.75)
189
+ end
190
+
191
+ def hours_done_per_lesson(dts, dte)
192
+ return ((dte - dts).to_f/0.75) * 24.0
193
+ end
194
+
195
+
196
+ end
197
+
198
+ end