timequiz 0.1.3

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,89 @@
1
+ #encoding: UTF-8
2
+ =begin
3
+ /***************************************************************************
4
+ * ©2011-2016 Michael Uplawski <michael.uplawski@uplawski.eu> *
5
+ * *
6
+ * This program is free software; you can redistribute it and/or modify *
7
+ * it under the terms of the GNU General Public License as published by *
8
+ * the Free Software Foundation; either version 3 of the License, or *
9
+ * (at your option) any later version. *
10
+ * *
11
+ * This program is distributed in the hope that it will be useful, *
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
+ * GNU General Public License for more details. *
15
+ * *
16
+ * You should have received a copy of the GNU General Public License *
17
+ * along with this program; if not, write to the *
18
+ * Free Software Foundation, Inc., *
19
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
20
+ ***************************************************************************/
21
+ =end
22
+
23
+ =begin
24
+ A module to facilitate frequently occuring checks on
25
+ file-system objects. You can certainly do without this,
26
+ but maybe you find it useful.
27
+ =end
28
+ module File_Checking
29
+
30
+ @@text_messages = {
31
+ :exist? => "does not exist!",
32
+ :exist => "does not exist!",
33
+ :readable? => "is not readable!",
34
+ :readable => "is not readable!",
35
+ :executable? => "is not executable!",
36
+ :executable => "is not executable!",
37
+ :writable? => "is not writable!",
38
+ :writable => "is not writable!",
39
+ :directory? => "is not a directory!",
40
+ :directory => "is not a directory!",
41
+ :file? => "is not a file!",
42
+ :file => "is not a file!",
43
+ }
44
+
45
+ # Checks if the file with the name from the first
46
+ # parameter has the properties, listed in the second.
47
+ # The messages parameter is an array of one or several
48
+ # of :exist?, :readable?, :writable?, :directory? or
49
+ # their string-representations, respectively.
50
+ # Returns nil in case of success, otherwise an
51
+ # informative message, describing the first negative
52
+ # test-result.
53
+ def file_check(file, *messages)
54
+ File_Checking.file_check(file, *messages)
55
+ end
56
+
57
+ # Checks if the file with the name from the first
58
+ # parameter has the properties, listed in the second.
59
+ # The messages parameter is an array of one or all
60
+ # of :exist?, :readable?, :writable?, :directory? or
61
+ # their string-representations, respectively.
62
+ # Returns nil in case of success, otherwise an
63
+ # informative message, describing the first negative
64
+ # test-result.
65
+ def self.file_check(file, *messages)
66
+ msg = nil
67
+ if(file && messages.respond_to?(:to_ary) && !messages.empty?)
68
+ messages.each do |k|
69
+ if(! k.to_s.end_with?('?'))
70
+ k = (k.to_s << '?').to_sym
71
+ end
72
+ @log.debug ('checking ' << k.to_s) if @log
73
+ if(msg == nil && File.respond_to?(k) && ! File.send(k, file.to_s))
74
+ msg = "#{file} #{@@text_messages[k.to_sym]}"
75
+ end
76
+ end
77
+ end
78
+ msg
79
+ end
80
+ alias :check_file :file_check
81
+ end
82
+
83
+ =begin
84
+ # example
85
+
86
+ include File_Checking
87
+ msg = file_check('some_file.txt', [:exist?, :readable?, 'writable'])
88
+ puts msg if msg
89
+ =end
@@ -0,0 +1,204 @@
1
+ #encoding: UTF-8
2
+ =begin
3
+ /***************************************************************************
4
+ * ©2011-2016 Michael Uplawski <michael.uplawski@uplawski.eu> *
5
+ * *
6
+ * This program is free software; you can redistribute it and/or modify *
7
+ * it under the terms of the GNU General Public License as published by *
8
+ * the Free Software Foundation; either version 3 of the License, or *
9
+ * (at your option) any later version. *
10
+ * *
11
+ * This program is distributed in the hope that it will be useful, *
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
+ * GNU General Public License for more details. *
15
+ * *
16
+ * You should have received a copy of the GNU General Public License *
17
+ * along with this program; if not, write to the *
18
+ * Free Software Foundation, Inc., *
19
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
20
+ ***************************************************************************/
21
+ =end
22
+ require 'logger'
23
+ require_relative 'file_checking'
24
+
25
+ =begin Creates a member @log and precede its output with the name of the class
26
+ of the object.
27
+ Example for a class-level logger:
28
+ # --------------------
29
+ class TClass
30
+ self.extend(Logging)
31
+ @@log = init_logger(STDOUT)
32
+ def test_log
33
+ @@log.info('class-level logger called from instance: ' << @@log.to_s)
34
+ @log = @@log
35
+ @log.info('AGAIN: class-level logger called from instance: ' << @log.to_s)
36
+ end
37
+ def self::test_log
38
+ @log.info('class-level logger called from class: ' << @log.to_s)
39
+ @@log.info('AGAIN: class-level logger called from class: ' << @@log.to_s)
40
+ end
41
+ end
42
+ #---------------------
43
+ Example for a object-level logger:
44
+ ATTN! This means 1 logger per object.
45
+ # --------------------
46
+ class TClass
47
+ include Logging
48
+ def initialize
49
+ init_logger(STDOUT, Logger::DEBUG)
50
+ end
51
+ def test_log
52
+ @log.debug('called test_log() ')
53
+ end
54
+ end
55
+ =end
56
+ module Logging
57
+ include File_Checking
58
+
59
+ @@have_log = false
60
+ @@LOG_CONF = File.dirname(File.absolute_path(__FILE__)) << File::Separator << 'log.conf'
61
+
62
+ # Call this method in an instance-method (e.g. initialize() ) to define the
63
+ # object-level logger; i.e. an object-specific member @log.
64
+ # Call this method within the class-definition for a class-level logger; i.e.
65
+ # a member @log for class-level acces.
66
+ # The method returns the logger, so you can actually do what you want with it.
67
+ def init_logger(target = STDOUT, level = Logger::INFO)
68
+ # Prepare for a class-level logger. This is actually quite cool.
69
+
70
+ # ---> Ingeniuous code starts here
71
+ cn = (self.class == Class ? name : self.class.name)
72
+ # <--- Ingeniuous code ends here
73
+
74
+ # allow to override the set log-levels with an
75
+ # external configuration (log.conf).
76
+ log_conf(cn)
77
+ # Or use the defaults as set here or elsewhere...
78
+
79
+ @level ||= level
80
+ @target ||= target
81
+
82
+ @log = Logger.new(@target)
83
+ @log.level = @level
84
+
85
+ @log.formatter = proc do |severity, datetime, progname, msg|
86
+ t = Time.now
87
+ "#{cn}: #{severity} #{t.hour}-#{t.min}-#{t.sec}: #{msg}\n"
88
+ end
89
+ if ! @@have_log
90
+ @log.debug cn.dup << ' reading logging-configuration from ' << @@LOG_CONF
91
+ @@have_log = true
92
+ @log.debug('level is ' << level.to_s)
93
+ end
94
+ return @log
95
+ end
96
+
97
+ def log_label=(label)
98
+ @log.formatter = proc do |severity, datetime, progname, msg|
99
+ t = Time.now
100
+ if(label && !label.empty?)
101
+ "#{label}: #{severity} #{t.hour}-#{t.min}-#{t.sec}: #{msg}\n"
102
+ else
103
+ "#{$0}: #{severity} #{t.hour}-#{t.min}-#{t.sec}: #{msg}\n"
104
+ end
105
+ end
106
+ end
107
+
108
+ # Set the log-target to an IO object.
109
+ def log_target=(target)
110
+ @target = target
111
+ @log = Logger.new(@@target)
112
+ @log.level = @level
113
+ end
114
+
115
+ # set the log-level
116
+ def log_level=(level)
117
+ @level = level
118
+ @log.level = @level
119
+ end
120
+
121
+ private
122
+
123
+ # Override or set the log-level and target-device, as set in a file 'log.conf'.
124
+ # I do not like the look of this, but it works just the way I want it to.
125
+ # "HEAVANS! Isn't there a standard way to do this in Ruby, for Christ's sake?", you say.
126
+ # Heck, I don't care. <= Read that again, I say.
127
+ def log_conf(cn = nil)
128
+ config = level = target = nil
129
+ # puts 'log-config is in ' << @@LOG_CONF
130
+ if(File::exist?(@@LOG_CONF) )
131
+ begin
132
+ conf = File.read(@@LOG_CONF)
133
+ config = instance_eval(conf)
134
+ rescue Exception => ex
135
+ STDERR.puts "WARNING! Cannot evaluate the logger-configuration!" << ' ' << ex.message
136
+ STDERR.puts "Default log-levels apply."
137
+ end
138
+ # else
139
+ # puts "Default log-levels apply."
140
+ end
141
+
142
+ if(config && config.respond_to?(:to_hash) )
143
+ config.default = nil
144
+ if cn
145
+ config = config[cn.to_sym]
146
+ else
147
+ config = config[self.class.name.to_sym]
148
+ end
149
+
150
+ if(config )
151
+ if(config.respond_to?(:to_ary) && config.size == 2)
152
+ @level, @target = config
153
+ @target.downcase!
154
+ logdir = File.dirname(@target)
155
+ msg = file_check(logdir, :exist?, :directory?, :writable?)
156
+ if(msg)
157
+ STDERR.puts "WARNING! A logfile for '%s' cannot be written to %s (%s)!" %[self.class.name, logdir, msg]
158
+ @target = nil
159
+ end
160
+ else
161
+ @level = config
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ ######### test
169
+ if __FILE__ == $0
170
+ class TClass
171
+ # class level ---->
172
+ self.extend(Logging)
173
+ @@log = init_logger(STDOUT, Logger::INFO)
174
+ # <------
175
+ # object-level ---->
176
+ include Logging
177
+ # <---------
178
+
179
+ def test_log
180
+ @@log.info('class-level logger called from instance: ' << @@log.to_s)
181
+ #@log = @@log # works too
182
+ @log = TClass.class_eval{@log}
183
+ @log.info('AGAIN: class-level logger called from instance: ' << @log.to_s)
184
+ @log.debug("you won't see this on log-level INFO")
185
+
186
+ # object-level ---->
187
+ init_logger
188
+ # <-----------
189
+ @log.info("That's a different thing: " << @log.to_s << " - object-level logger!")
190
+
191
+ end
192
+ def self::test_log
193
+ @log.info('class-level logger called from class: ' << @log.to_s)
194
+ @@log.info('AGAIN: class-level logger called from class: ' << @log.to_s)
195
+ end
196
+ end
197
+
198
+ TClass.new.test_log # class-logger + 1st object-logger
199
+ TClass.new.test_log # same class-logger + 2nd object-logger
200
+
201
+ TClass::test_log # same class-logger
202
+ puts 'And just say it once clearly: THIS IS COOOL!!'
203
+ end
204
+ #EOF
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env ruby
2
+ #encoding: UTF-8
3
+
4
+ =begin
5
+ /******************************************************************************
6
+ * Copyright © 2017-2017, Michael Uplawski <michael.uplawski@uplawski.eu> *
7
+ * *
8
+ * This program is free software; you can redistribute it and/or modify *
9
+ * it under the terms of the GNU General Public License as published by *
10
+ * the Free Software Foundation; either version 3 of the License, or *
11
+ * (at your option) any later version. *
12
+ * *
13
+ * This program is distributed in the hope that it will be useful, *
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16
+ * GNU General Public License for more details. *
17
+ * *
18
+ * You should have received a copy of the GNU General Public License *
19
+ * along with this program; if not, write to the *
20
+ * Free Software Foundation, Inc., *
21
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
22
+ ******************************************************************************/
23
+ =end
24
+
25
+ require_relative 'argparser'
26
+ require_relative 'adder'
27
+ require_relative 'color_output'
28
+ require_relative 'extstring'
29
+ require_relative 'user_input'
30
+ require_relative 'busy_indicator'
31
+ require_relative 'event'
32
+ require_relative 'logging'
33
+
34
+ require 'date'
35
+
36
+ # Main program class. All action is within the initialize() method, or, more
37
+ # precisely in the start_game() method which has to be defined by an
38
+ # interface-module.
39
+ class Timequiz
40
+ self::extend(Logging)
41
+ @@log = self::init_logger
42
+
43
+ # Find out, how the program has been called.
44
+ # In juin 2017 this can be 'timequiz' or 'timequizGtk'.
45
+ # Write a Qt-interface, create a symbolic link 'timequizQt' in
46
+ # ../../bin and add a when-clause to the case-when structure, right below the
47
+ # method (in class-scope).
48
+ # Returns true if the given executable file is named the same as the one
49
+ # called, false if they are not the same.
50
+ # Returns the name of the called executable, if no argument is given.
51
+ def self.called_as?(executable = nil)
52
+ return File::basename($0) == executable.strip if executable
53
+ return File::basename($0)
54
+ end
55
+
56
+ # This adds all the functionality of the named interface module to the
57
+ # current class.
58
+ case called_as?
59
+ # GTK-Interface
60
+ when 'timequizGtk'
61
+ # require_relative 'gtk/timequizGtk'
62
+ # include TimequizGtk
63
+ @log.info('I am sorry. But there is not Gtk-interface, for the time.')
64
+ # Text only
65
+ when 'timequiz'
66
+ require_relative 'console'
67
+ include Console
68
+ # EXAMPLE
69
+ # when 'timequizSWT'
70
+ # require_relative 'swt'
71
+ # include SWT
72
+ else
73
+ # beats me
74
+ # puts "How did you do that?"
75
+ # ... HEY! I guess: You created a link to the executable but forgot
76
+ # something else. Read the comment to the 'called_as?' method, above.
77
+ @log.error(": A user-interface \"" << called_as? << "\" has not (yet) been defined (read the code, my friend)! Aborting.")
78
+ exit false
79
+ end
80
+
81
+ def verify_prepare(file)
82
+ if(file && !file.empty? && !file.end_with?('.rb'))
83
+ file << ".rb"
84
+ end
85
+ begin
86
+ ofl = File.open(file, 'a')
87
+ if !File.readable?(file)
88
+ @log.error('The file ' << file << ' cannot be read! Aborting')
89
+ exit false
90
+ end
91
+ if(File.empty?(file) )
92
+ @log.info("The file " << file << " is empty. You must add some events to it.")
93
+ if(File.writable?(file) )
94
+ orig_file = File.dirname(__FILE__) << File::Separator << 'events.rb'
95
+ @log.debug('orig_file is ' << orig_file)
96
+ File.open(orig_file, 'r') do |ifl|
97
+ ofl.puts("# Events defined for the Timequiz game")
98
+ ofl.puts("# Lines starting with '#' are comments.\n\n# EXAMPLES:")
99
+ 3.times do
100
+ line = ""
101
+ line = ifl.readline.strip until line.start_with?('$')
102
+ ofl.puts('#' << line)
103
+ end
104
+ ofl.puts("\n# Add your own events below this line\n#" << '_' * 60)
105
+ end
106
+ ofl.close
107
+ end
108
+ add_ok = require file
109
+ else
110
+ add_ok = require file
111
+ if(add_ok)
112
+ return add_ok, $events.length >= 3
113
+ end
114
+ end
115
+ rescue IOError => ex
116
+ ofl.close
117
+ @log.error('Cannot work with the given file: ' << ex.message)
118
+ exit false
119
+ end
120
+ end
121
+
122
+ def initialize(*args)
123
+ @log = @@log
124
+ # adjust log-level, if given.
125
+ # Else use what you know.
126
+ options = ArgParser.parse(*args)
127
+ $LOG_LEVEL = Logger::DEBUG if options.debug
128
+ @log.level = $LOG_LEVEL if $LOG_LEVEL
129
+ @log.debug('log_level is ' << $LOG_LEVEL.to_s)
130
+ @log.debug('options are ' << options.to_s)
131
+ # should we add only a new event to the list
132
+ @log.debug('options are : ' << options.to_s)
133
+ add_ok = game_ok = false
134
+ if(options.file)
135
+ @log.debug('shall use non-standard events file')
136
+ add_ok, game_ok = verify_prepare(options.file)
137
+ else
138
+ game_ok = require_relative 'events'
139
+ end
140
+ if(options.add)
141
+ if(add_ok)
142
+ Adder::add(options)
143
+ exit true
144
+ elsif(options.file)
145
+ @log.error('cannot add events to ' << options.file)
146
+ exit false
147
+ else
148
+ @log.error('PSE start the program with -h or --help to see an option-overview')
149
+ exit false
150
+ end
151
+ end
152
+
153
+ # start the chosen interface
154
+ if(game_ok)
155
+ start_game
156
+ else
157
+ @log.error("Cannot play with the events in " << options.file)
158
+ @log.error("PSE verify that there are at least 3 events defined!")
159
+ exit false
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ # A dangerous method. Not only because the use of the argument is somewhat
166
+ # esotheric... This sorts the events in the *correct* order of their years.
167
+ # Any later comparison with user-provided values is based on the *correct*
168
+ # order, not the one currently at display! Although this is of interest only
169
+ # after the very first round, it caused me some headache, once the
170
+ # user-interfaces had been separated... Mind this when creating a new UI.
171
+ # The argument 'index' controlls if the displayed index should be changed to
172
+ # mirror the *correct* order of events, meaning the index from the Array + 1
173
+ # (i.e. [1,2,3...]). I forgot, how this is useful (see in 'good_order',
174
+ # below).
175
+ def sort_years(index = false)
176
+ @m_events.sort! {|f, s| f.year <=> s.year }
177
+ @m_events.each_with_index {|ev, i| ev.disp_index = i+1} if index
178
+ end
179
+
180
+ # Verifies and reports if the user-provided order is correct in the way that
181
+ # it creates a consistent timeline. The argument 'response' may be either an
182
+ # Array of Integers or just one Integer. In the latter case, an Event should
183
+ # be given, too.
184
+ def good_order(response, event = nil)
185
+ if(response.respond_to?(:to_ary) )
186
+ response_years = []
187
+ response.each_with_index {|r, i| response_years[r-1] = @m_events[i].year}
188
+ @log.debug('response is ' << response.join(', ') << "\nresponse_years is : " << response_years.join(', ') )
189
+
190
+ # do not change the displayed index
191
+ sort_years(false)
192
+ event_years = @m_events.collect{|ev| ev.year }
193
+
194
+ # Compare the order of years, not the order of events!
195
+ # There may be two or more events in the same year.
196
+ response_years == event_years
197
+ elsif(response.respond_to?(:to_int) )
198
+ # set/change the displayed index
199
+ sort_years(true)
200
+ # Compare the order of years, not the order of events!
201
+ # There may be two or more events in the same year.
202
+ @m_events[response + 1].year == event.year
203
+ end
204
+ end
205
+
206
+
207
+ end
208
+
209
+
210
+ ## ---TEST
211
+ if __FILE__ == $0
212
+ qu = Timequiz.new
213
+ end
214
+
215
+ ## ---END TEST
216
+ # EOF, too.