BxMS 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/bin/bxms +8 -0
  2. data/bin/bxms.bat +3 -0
  3. data/bin/bxms.rb +8 -0
  4. data/bin/bxms.sh +3 -0
  5. data/examples/www.berlinfactor.com/Bx.css +317 -0
  6. data/examples/www.berlinfactor.com/Bx.js +39 -0
  7. data/examples/www.berlinfactor.com/Section.Books/books.txt +198 -0
  8. data/examples/www.berlinfactor.com/Section.Books/index.page +18 -0
  9. data/examples/www.berlinfactor.com/Section.Bx/index.page +18 -0
  10. data/examples/www.berlinfactor.com/Section.Contact/index.page +12 -0
  11. data/examples/www.berlinfactor.com/Section.Links/Game.Development.txt +6 -0
  12. data/examples/www.berlinfactor.com/Section.Links/News.and.Stuff.txt +10 -0
  13. data/examples/www.berlinfactor.com/Section.Links/Software.Development.txt +25 -0
  14. data/examples/www.berlinfactor.com/Section.Links/Symbian.Development.txt +22 -0
  15. data/examples/www.berlinfactor.com/Section.Links/Tools.and.Utilities.txt +15 -0
  16. data/examples/www.berlinfactor.com/Section.Links/index.page +31 -0
  17. data/examples/www.berlinfactor.com/favicon.ico +0 -0
  18. data/examples/www.berlinfactor.com/images/background.png +0 -0
  19. data/examples/www.berlinfactor.com/images/logo_above.gif +0 -0
  20. data/examples/www.berlinfactor.com/images/logo_below.gif +0 -0
  21. data/examples/www.berlinfactor.com/images/logo_clickable.gif +0 -0
  22. data/examples/www.berlinfactor.com/images/no_section_name.gif +0 -0
  23. data/examples/www.berlinfactor.com/images/section_books.gif +0 -0
  24. data/examples/www.berlinfactor.com/images/section_books_hover.gif +0 -0
  25. data/examples/www.berlinfactor.com/images/section_books_name.gif +0 -0
  26. data/examples/www.berlinfactor.com/images/section_bx.gif +0 -0
  27. data/examples/www.berlinfactor.com/images/section_bx_hover.gif +0 -0
  28. data/examples/www.berlinfactor.com/images/section_bx_name.gif +0 -0
  29. data/examples/www.berlinfactor.com/images/section_contact.gif +0 -0
  30. data/examples/www.berlinfactor.com/images/section_contact_hover.gif +0 -0
  31. data/examples/www.berlinfactor.com/images/section_contact_name.gif +0 -0
  32. data/examples/www.berlinfactor.com/images/section_links.gif +0 -0
  33. data/examples/www.berlinfactor.com/images/section_links_hover.gif +0 -0
  34. data/examples/www.berlinfactor.com/images/section_links_name.gif +0 -0
  35. data/examples/www.berlinfactor.com/images/section_tfdj.gif +0 -0
  36. data/examples/www.berlinfactor.com/images/section_tfdj_hover.gif +0 -0
  37. data/examples/www.berlinfactor.com/images/section_tfdj_name.gif +0 -0
  38. data/examples/www.berlinfactor.com/index.page +21 -0
  39. data/examples/www.berlinfactor.com/news.page +11 -0
  40. data/examples/www.berlinfactor.com/news.txt +210 -0
  41. data/src/bxms.rb +61 -0
  42. data/src/bxms/context.rb +96 -0
  43. data/src/bxms/functions.rb +39 -0
  44. data/src/bxms/processor.rb +53 -0
  45. data/src/bxms/root_state.rb +30 -0
  46. data/src/bxms/rules.rb +82 -0
  47. data/src/bxms/state.rb +105 -0
  48. data/src/bxms/tags.rb +108 -0
  49. data/src/bxms/variables.rb +9 -0
  50. data/src/page/part.rb +51 -0
  51. data/src/page/part_collector.rb +55 -0
  52. data/src/rules/base.rb +16 -0
  53. data/src/rules/copy.rb +17 -0
  54. data/src/rules/ignore.rb +14 -0
  55. data/src/rules/process.rb +23 -0
  56. data/src/rules/process_page.rb +69 -0
  57. data/src/util/chained_hash.rb +41 -0
  58. data/src/util/logger.rb +710 -0
  59. data/src/util/system_logger.rb +30 -0
  60. metadata +116 -0
data/src/bxms/tags.rb ADDED
@@ -0,0 +1,108 @@
1
+
2
+ require 'util/chained_hash'
3
+ require 'util/system_logger'
4
+
5
+ module BxMS
6
+
7
+ class Tags < Util::ChainedHash
8
+
9
+ def initialize( parent = nil )
10
+ super parent
11
+ @logger = Util::SystemLogger.new 'BxMS::Tags'
12
+ end
13
+
14
+ def load( file_name )
15
+ return unless File.file? file_name
16
+ tag_code = IO.read file_name
17
+ tag = create_tag_for file_name
18
+ tag.init tag_code, file_name
19
+ self[ tag.name ] = tag
20
+ @logger.info "Loaded tag #{tag.name} from #{file_name}"
21
+ end
22
+
23
+ def create_tag_for( file_name )
24
+ tag_suffix = /\A.+\.([^\.]+)\Z/.match( file_name )[ 1 ]
25
+
26
+ clazz = TAG_CLASSES[ tag_suffix ]
27
+ clazz = SimpleTag unless clazz
28
+
29
+ tag_name = File.basename file_name, '.*'
30
+ return clazz.new( tag_name )
31
+ end
32
+
33
+ end
34
+
35
+
36
+ class TagBase
37
+
38
+ attr_reader :name
39
+
40
+ def initialize( name )
41
+ @name = name
42
+ end
43
+
44
+ end
45
+
46
+
47
+ class RubyTag < TagBase
48
+
49
+ def init( code, file_name )
50
+ @code = code
51
+ @file_name = file_name
52
+ end
53
+
54
+ def define_in( context )
55
+ definition = "def #{name}\n"
56
+ definition << " #{@code}\n"
57
+ definition << "end\n"
58
+ eval definition, context.get_binding, @file_name
59
+ end
60
+
61
+ end
62
+
63
+
64
+ class SmartTag < TagBase
65
+
66
+ def init( code, file_name )
67
+ @code = code
68
+ @file_name = file_name
69
+ end
70
+
71
+ def define_in( context )
72
+ lines = @code.split "\n"
73
+ first_line = lines.delete_at 0
74
+
75
+ signature = first_line.sub 'PARAMETERS:', ''
76
+
77
+ definition = "def #{name}( #{signature} )\n"
78
+ lines.each do |line|
79
+ definition << " #{line}\n"
80
+ end
81
+ definition << "end\n"
82
+
83
+ eval definition, context.get_binding, @file_name
84
+ end
85
+
86
+ end
87
+
88
+
89
+ class SimpleTag < TagBase
90
+
91
+ def init( code, file_name )
92
+ @code = code
93
+ @file_name = file_name
94
+ end
95
+
96
+ def define_in( context )
97
+ the_code = @code
98
+ the_file_name = @file_name
99
+ block = Proc.new { run_erb_on the_code, the_file_name }
100
+ context.class.send :define_method, name, block
101
+ end
102
+
103
+ end
104
+
105
+
106
+ TAG_CLASSES = { 'rb' => RubyTag, 'tag' => SmartTag }
107
+
108
+ end
@@ -0,0 +1,9 @@
1
+
2
+ require 'util/chained_hash'
3
+
4
+ module BxMS
5
+
6
+ class Variables < Util::ChainedHash
7
+ end
8
+
9
+ end
data/src/page/part.rb ADDED
@@ -0,0 +1,51 @@
1
+
2
+ require 'erb'
3
+
4
+ module Page
5
+
6
+ class Part
7
+
8
+ attr_writer :base_name
9
+ attr_reader :name
10
+
11
+ def initialize( name, type )
12
+ type = :RUBY_LINES unless type
13
+ @name = name
14
+ @type = type.to_sym
15
+ @lines = []
16
+ end
17
+
18
+ def process( context )
19
+ method_name = "process_#{@type.to_s.downcase}"
20
+ send method_name, context
21
+ end
22
+
23
+ def process_ruby( context )
24
+ context.eval_ruby @lines.join, file_name
25
+ end
26
+
27
+ def process_ruby_lines( context )
28
+ body = ''
29
+ @lines.each_index do |idx|
30
+ data = context.eval_ruby @lines[ idx ], file_name, idx
31
+ body += "#{data.to_s}\n" if data
32
+ end
33
+ body
34
+ end
35
+
36
+ def process_erb( context )
37
+ template = @lines.join
38
+ context.run_erb_on template, file_name
39
+ end
40
+
41
+ def file_name
42
+ "#{@base_name}#[#{@name}]"
43
+ end
44
+
45
+ def <<( line )
46
+ @lines << line
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,55 @@
1
+
2
+ require 'page/part'
3
+
4
+ module Rules
5
+
6
+ class PartCollector
7
+
8
+ MAIN_PART = 'Main'
9
+ PART_SPEC = /^\[([^:]+)(?:\:(.+))?\]$/
10
+
11
+ def initialize( page_file )
12
+ @page_file = page_file
13
+ @parts = {}
14
+ end
15
+
16
+ def main_part
17
+ @parts[ MAIN_PART ]
18
+ end
19
+
20
+ def load_and_scan( file_name )
21
+ scan IO.read( file_name )
22
+ end
23
+
24
+ def scan( page_data )
25
+ active_part = init_part "[#{MAIN_PART}]"
26
+ page_data.each_line do |line|
27
+ if is_start_of_part? line
28
+ active_part = init_part line
29
+ else
30
+ active_part << line
31
+ end
32
+ end
33
+ @parts
34
+ end
35
+
36
+ def []( part_name )
37
+ @parts[ part_name ]
38
+ end
39
+
40
+ protected
41
+
42
+ def init_part( part_spec )
43
+ spec = PART_SPEC.match part_spec
44
+ part = Page::Part.new spec[ 1 ], spec[ 2 ]
45
+ part.base_name = @page_file
46
+ @parts[ part.name ] = part
47
+ end
48
+
49
+ def is_start_of_part?( line )
50
+ PART_SPEC =~ line
51
+ end
52
+
53
+ end
54
+
55
+ end
data/src/rules/base.rb ADDED
@@ -0,0 +1,16 @@
1
+
2
+ module Rules
3
+
4
+ class BaseRule
5
+
6
+ attr_writer :verbose
7
+
8
+ def name
9
+ name = self.class.name
10
+ name.sub! /.+\:\:/, ''
11
+ name.sub! /Rule\Z/, ''
12
+ end
13
+
14
+ end
15
+
16
+ end
data/src/rules/copy.rb ADDED
@@ -0,0 +1,17 @@
1
+
2
+ require 'fileutils'
3
+ require 'rules/base'
4
+
5
+ module Rules
6
+
7
+ class CopyRule < BaseRule
8
+
9
+ def apply_to( file, state )
10
+ puts "copy #{file} to #{state.output_dir}" if @verbose
11
+ FileUtils.mkdir_p state.output_dir unless File.directory? state.output_dir
12
+ FileUtils.cp file, state.output_dir
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,14 @@
1
+
2
+ require 'rules/base'
3
+
4
+ module Rules
5
+
6
+ class IgnoreRule < BaseRule
7
+
8
+ def apply_to( file, state )
9
+ puts "ignore #{file}" if @verbose
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,23 @@
1
+
2
+ require 'bxms/context'
3
+ require 'rules/base'
4
+
5
+ module Rules
6
+
7
+ class ProcessRule < BaseRule
8
+
9
+ def apply_to( input_file, state )
10
+ puts "process #{input_file} to #{state.output_dir}" if @verbose
11
+
12
+ file_contents = IO.read input_file
13
+
14
+ context = BxMS::Context.new input_file, state
15
+ output = context.run_erb_on file_contents, input_file
16
+
17
+ output_file = state.get_output_file_for input_file
18
+ File.open( output_file, 'w' ) { |file| file.write output }
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,69 @@
1
+
2
+ require 'bxms/context'
3
+ require 'page/part_collector'
4
+ require 'rules/base'
5
+
6
+ module Rules
7
+
8
+ class ProcessPageRule < BaseRule
9
+
10
+ def apply_to( input_file, state )
11
+ puts "process page #{input_file} to #{state.output_dir}" if @verbose
12
+
13
+ collector = PartCollector.new input_file
14
+ parts = collector.load_and_scan input_file
15
+
16
+ context = BxMS::Context.new input_file, state
17
+ part_provider = PagePartProvider.new( parts, context )
18
+ context.set_variable :page, part_provider
19
+
20
+ context.add_method( :page_include ) do |file_name,*args|
21
+ args[0].each_pair { |k,v| context.set_variable k, v } unless args.empty?
22
+ collector.scan context.load_file( file_name )
23
+ collector.main_part.process context
24
+ end
25
+ context.add_method( :part_available? ) do |part_name|
26
+ part = part_provider[ part_name ]
27
+ part
28
+ end
29
+ context.add_method( :part_empty? ) do |part_name|
30
+ part = part_provider[ part_name ]
31
+ !part || part.strip.size == 0
32
+ end
33
+
34
+ output = collector.main_part.process context
35
+
36
+ output_file = state.get_output_file_for input_file
37
+ output_file.sub! /\.page$/, '.html'
38
+
39
+ File.open( output_file, 'w' ) do |file|
40
+ file.write output
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+
47
+ class PagePartProvider
48
+
49
+ def initialize( parts, context )
50
+ @parts = parts
51
+ @context = context
52
+ end
53
+
54
+ def []( key )
55
+ part = @parts[ key ]
56
+ part = @parts[ key.to_s ] unless part
57
+ part = @parts[ key.to_sym ] unless part
58
+ return nil unless part
59
+ return part unless part.is_a? Page::Part
60
+ part.process @context
61
+ end
62
+
63
+ def []=( key, val )
64
+ @parts[ key ] = val
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,41 @@
1
+
2
+ module Util
3
+
4
+ class ChainedHash
5
+
6
+ def initialize( parent = nil )
7
+ @parent = parent || @parent = {}
8
+ @values = {}
9
+ end
10
+
11
+ def merge!( other_hash )
12
+ @values.merge!( other_hash )
13
+ end
14
+
15
+ def has_key?( key )
16
+ @values.has_key?( key ) || @parent.has_key?( key )
17
+ end
18
+
19
+ def each_key( &block )
20
+ @values.each_key { |k| yield k }
21
+ @parent.each_key { |k| yield k }
22
+ end
23
+
24
+ def each_pair( &block )
25
+ @values.each_pair { |k,v| yield k,v }
26
+ @parent.each_pair { |k,v| yield k,v }
27
+ end
28
+
29
+ def []( key )
30
+ val = @values[ key ]
31
+ val = @parent[ key ] unless val
32
+ val
33
+ end
34
+
35
+ def []=( key, val )
36
+ @values[ key ] = val
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,710 @@
1
+ # logger.rb - tweaked logger - taken from the ruby lib
2
+
3
+
4
+ # = logger.rb
5
+ #
6
+ # Simple logging utility.
7
+ #
8
+ # Author:: NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>
9
+ # Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair
10
+ # License::
11
+ # You can redistribute it and/or modify it under the same terms of Ruby's
12
+ # license; either the dual license version in 2003, or any later version.
13
+ # Revision:: $Id: logger.rb,v 1.1 2006/04/03 17:25:40 lukic Exp $
14
+ #
15
+ # See Logger for documentation.
16
+ #
17
+
18
+
19
+ #
20
+ # == Description
21
+ #
22
+ # The Logger class provides a simple but sophisticated logging utility that
23
+ # anyone can use because it's included in the Ruby 1.8.x standard library.
24
+ #
25
+ # The HOWTOs below give a code-based overview of Logger's usage, but the basic
26
+ # concept is as follows. You create a Logger object (output to a file or
27
+ # elsewhere), and use it to log messages. The messages will have varying
28
+ # levels (+info+, +error+, etc), reflecting their varying importance. The
29
+ # levels, and their meanings, are:
30
+ #
31
+ # +FATAL+:: an unhandleable error that results in a program crash
32
+ # +ERROR+:: a handleable error condition
33
+ # +WARN+:: a warning
34
+ # +INFO+:: generic (useful) information about system operation
35
+ # +DEBUG+:: low-level information for developers
36
+ #
37
+ # So each message has a level, and the Logger itself has a level, which acts
38
+ # as a filter, so you can control the amount of information emitted from the
39
+ # logger without having to remove actual messages.
40
+ #
41
+ # For instance, in a production system, you may have your logger(s) set to
42
+ # +INFO+ (or +WARN+ if you don't want the log files growing large with
43
+ # repetitive information). When you are developing it, though, you probably
44
+ # want to know about the program's internal state, and would set them to
45
+ # +DEBUG+.
46
+ #
47
+ # === Example
48
+ #
49
+ # A simple example demonstrates the above explanation:
50
+ #
51
+ # log = Logger.new(STDOUT)
52
+ # log.level = Logger::WARN
53
+ #
54
+ # log.debug("Created logger")
55
+ # log.info("Program started")
56
+ # log.warn("Nothing to do!")
57
+ #
58
+ # begin
59
+ # File.each_line(path) do |line|
60
+ # unless line =~ /^(\w+) = (.*)$/
61
+ # log.error("Line in wrong format: #{line}")
62
+ # end
63
+ # end
64
+ # rescue => err
65
+ # log.fatal("Caught exception; exiting")
66
+ # log.fatal(err)
67
+ # end
68
+ #
69
+ # Because the Logger's level is set to +WARN+, only the warning, error, and
70
+ # fatal messages are recorded. The debug and info messages are silently
71
+ # discarded.
72
+ #
73
+ # === Features
74
+ #
75
+ # There are several interesting features that Logger provides, like
76
+ # auto-rolling of log files, setting the format of log messages, and
77
+ # specifying a program name in conjunction with the message. The next section
78
+ # shows you how to achieve these things.
79
+ #
80
+ #
81
+ # == HOWTOs
82
+ #
83
+ # === How to create a logger
84
+ #
85
+ # The options below give you various choices, in more or less increasing
86
+ # complexity.
87
+ #
88
+ # 1. Create a logger which logs messages to STDERR/STDOUT.
89
+ #
90
+ # logger = Logger.new(STDERR)
91
+ # logger = Logger.new(STDOUT)
92
+ #
93
+ # 2. Create a logger for the file which has the specified name.
94
+ #
95
+ # logger = Logger.new('logfile.log')
96
+ #
97
+ # 3. Create a logger for the specified file.
98
+ #
99
+ # file = File.open('foo.log', File::WRONLY | File::APPEND)
100
+ # # To create new (and to remove old) logfile, add File::CREAT like;
101
+ # # file = open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
102
+ # logger = Logger.new(file)
103
+ #
104
+ # 4. Create a logger which ages logfile once it reaches a certain size. Leave
105
+ # 10 "old log files" and each file is about 1,024,000 bytes.
106
+ #
107
+ # logger = Logger.new('foo.log', 10, 1024000)
108
+ #
109
+ # 5. Create a logger which ages logfile daily/weekly/monthly.
110
+ #
111
+ # logger = Logger.new('foo.log', 'daily')
112
+ # logger = Logger.new('foo.log', 'weekly')
113
+ # logger = Logger.new('foo.log', 'monthly')
114
+ #
115
+ # === How to log a message
116
+ #
117
+ # Notice the different methods (+fatal+, +error+, +info+) being used to log
118
+ # messages of various levels. Other methods in this family are +warn+ and
119
+ # +debug+. +add+ is used below to log a message of an arbitrary (perhaps
120
+ # dynamic) level.
121
+ #
122
+ # 1. Message in block.
123
+ #
124
+ # logger.fatal { "Argument 'foo' not given." }
125
+ #
126
+ # 2. Message as a string.
127
+ #
128
+ # logger.error "Argument #{ @foo } mismatch."
129
+ #
130
+ # 3. With progname.
131
+ #
132
+ # logger.info('initialize') { "Initializing..." }
133
+ #
134
+ # 4. With severity.
135
+ #
136
+ # logger.add(Logger::FATAL) { 'Fatal error!' }
137
+ #
138
+ # === How to close a logger
139
+ #
140
+ # logger.close
141
+ #
142
+ # === Setting severity threshold
143
+ #
144
+ # 1. Original interface.
145
+ #
146
+ # logger.sev_threshold = Logger::WARN
147
+ #
148
+ # 2. Log4r (somewhat) compatible interface.
149
+ #
150
+ # logger.level = Logger::INFO
151
+ #
152
+ # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
153
+ #
154
+ #
155
+ # == Format
156
+ #
157
+ # Log messages are rendered in the output stream in a certain format. The
158
+ # default format and a sample are shown below:
159
+ #
160
+ # Log format:
161
+ # SeverityID, [Date Time mSec #pid] SeverityLabel -- ProgName: message
162
+ #
163
+ # Log sample:
164
+ # I, [Wed Mar 03 02:34:24 JST 1999 895701 #19074] INFO -- Main: info.
165
+ #
166
+ # You may change the date and time format in this manner:
167
+ #
168
+ # logger.datetime_format = "%Y-%m-%d %H:%M:%S"
169
+ # # e.g. "2004-01-03 00:54:26"
170
+ #
171
+ # There is currently no supported way to change the overall format, but you may
172
+ # have some luck hacking the Format constant.
173
+ #
174
+
175
+ require 'monitor'
176
+
177
+
178
+
179
+ module Util
180
+
181
+ class Logger
182
+ VERSION = "1.2.6"
183
+ /: (\S+),v (\S+)/ =~ %q$Id: logger.rb,v 1.1 2006/04/03 17:25:40 lukic Exp $
184
+ ProgName = "#{$1}/#{$2}"
185
+
186
+ class Error < RuntimeError; end
187
+ class ShiftingError < Error; end
188
+
189
+ # Logging severity.
190
+ module Severity
191
+ DEBUG = 0
192
+ INFO = 1
193
+ WARN = 2
194
+ ERROR = 3
195
+ FATAL = 4
196
+ UNKNOWN = 5
197
+ end
198
+ include Severity
199
+
200
+ # Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
201
+ attr_accessor :level
202
+
203
+ # Logging program name.
204
+ attr_accessor :progname
205
+
206
+ # Logging date-time format (string passed to +strftime+).
207
+ def datetime_format=(datetime_format)
208
+ @default_formatter.datetime_format = datetime_format
209
+ end
210
+
211
+ def datetime_format
212
+ @default_formatter.datetime_format
213
+ end
214
+
215
+ # Logging formatter. formatter#call is invoked with 4 arguments; severity,
216
+ # time, progname and msg for each log. Bear in mind that time is a Time and
217
+ # msg is an Object that user passed and it could not be a String. It is
218
+ # expected to return a logdev#write-able Object. Default formatter is used
219
+ # when no formatter is set.
220
+ attr_accessor :formatter
221
+
222
+ alias sev_threshold level
223
+ alias sev_threshold= level=
224
+
225
+ # Returns +true+ iff the current severity level allows for the printing of
226
+ # +DEBUG+ messages.
227
+ def debug?; @level <= DEBUG; end
228
+
229
+ # Returns +true+ iff the current severity level allows for the printing of
230
+ # +INFO+ messages.
231
+ def info?; @level <= INFO; end
232
+
233
+ # Returns +true+ iff the current severity level allows for the printing of
234
+ # +WARN+ messages.
235
+ def warn?; @level <= WARN; end
236
+
237
+ # Returns +true+ iff the current severity level allows for the printing of
238
+ # +ERROR+ messages.
239
+ def error?; @level <= ERROR; end
240
+
241
+ # Returns +true+ iff the current severity level allows for the printing of
242
+ # +FATAL+ messages.
243
+ def fatal?; @level <= FATAL; end
244
+
245
+ #
246
+ # === Synopsis
247
+ #
248
+ # Logger.new(name, shift_age = 7, shift_size = 1048576)
249
+ # Logger.new(name, shift_age = 'weekly')
250
+ #
251
+ # === Args
252
+ #
253
+ # +logdev+::
254
+ # The log device. This is a filename (String) or IO object (typically
255
+ # +STDOUT+, +STDERR+, or an open file).
256
+ # +shift_age+::
257
+ # Number of old log files to keep, *or* frequency of rotation (+daily+,
258
+ # +weekly+ or +monthly+).
259
+ # +shift_size+::
260
+ # Maximum logfile size (only applies when +shift_age+ is a number).
261
+ #
262
+ # === Description
263
+ #
264
+ # Create an instance.
265
+ #
266
+ def initialize(logdev, shift_age = 0, shift_size = 1048576)
267
+ @progname = nil
268
+ @level = DEBUG
269
+ @default_formatter = Formatter.new
270
+ @formatter = nil
271
+ @logdev = nil
272
+ if logdev
273
+ @logdev = LogDevice.new(logdev, :shift_age => shift_age,
274
+ :shift_size => shift_size)
275
+ end
276
+ end
277
+
278
+ #
279
+ # === Synopsis
280
+ #
281
+ # Logger#add(severity, message = nil, progname = nil) { ... }
282
+ #
283
+ # === Args
284
+ #
285
+ # +severity+::
286
+ # Severity. Constants are defined in Logger namespace: +DEBUG+, +INFO+,
287
+ # +WARN+, +ERROR+, +FATAL+, or +UNKNOWN+.
288
+ # +message+::
289
+ # The log message. A String or Exception.
290
+ # +progname+::
291
+ # Program name string. Can be omitted. Treated as a message if no +message+ and
292
+ # +block+ are given.
293
+ # +block+::
294
+ # Can be omitted. Called to get a message string if +message+ is nil.
295
+ #
296
+ # === Return
297
+ #
298
+ # +true+ if successful, +false+ otherwise.
299
+ #
300
+ # When the given severity is not high enough (for this particular logger), log
301
+ # no message, and return +true+.
302
+ #
303
+ # === Description
304
+ #
305
+ # Log a message if the given severity is high enough. This is the generic
306
+ # logging method. Users will be more inclined to use #debug, #info, #warn,
307
+ # #error, and #fatal.
308
+ #
309
+ # <b>Message format</b>: +message+ can be any object, but it has to be
310
+ # converted to a String in order to log it. Generally, +inspect+ is used
311
+ # if the given object is not a String.
312
+ # A special case is an +Exception+ object, which will be printed in detail,
313
+ # including message, class, and backtrace. See #msg2str for the
314
+ # implementation if required.
315
+ #
316
+ # === Bugs
317
+ #
318
+ # * Logfile is not locked.
319
+ # * Append open does not need to lock file.
320
+ # * But on the OS which supports multi I/O, records possibly be mixed.
321
+ #
322
+ def add(severity, message = nil, progname = nil, &block)
323
+ severity ||= UNKNOWN
324
+ if @logdev.nil? or severity < @level
325
+ return true
326
+ end
327
+ progname ||= @progname
328
+ if message.nil?
329
+ if block_given?
330
+ message = yield
331
+ else
332
+ message = progname
333
+ progname = @progname
334
+ end
335
+ end
336
+ @logdev.write(
337
+ format_message(format_severity(severity), Time.now, progname, message))
338
+ true
339
+ end
340
+ alias log add
341
+
342
+ #
343
+ # Dump given message to the log device without any formatting. If no log
344
+ # device exists, return +nil+.
345
+ #
346
+ def <<(msg)
347
+ unless @logdev.nil?
348
+ @logdev.write(msg)
349
+ end
350
+ end
351
+
352
+ #
353
+ # Log a +DEBUG+ message.
354
+ #
355
+ # See #info for more information.
356
+ #
357
+ def debug(progname = nil, &block)
358
+ add(DEBUG, nil, progname, &block)
359
+ end
360
+
361
+ #
362
+ # Log an +INFO+ message.
363
+ #
364
+ # The message can come either from the +progname+ argument or the +block+. If
365
+ # both are provided, then the +block+ is used as the message, and +progname+
366
+ # is used as the program name.
367
+ #
368
+ # === Examples
369
+ #
370
+ # logger.info("MainApp") { "Received connection from #{ip}" }
371
+ # # ...
372
+ # logger.info "Waiting for input from user"
373
+ # # ...
374
+ # logger.info { "User typed #{input}" }
375
+ #
376
+ # You'll probably stick to the second form above, unless you want to provide a
377
+ # program name (which you can do with <tt>Logger#progname=</tt> as well).
378
+ #
379
+ # === Return
380
+ #
381
+ # See #add.
382
+ #
383
+ def info(progname = nil, &block)
384
+ add(INFO, nil, progname, &block)
385
+ end
386
+
387
+ #
388
+ # Log a +WARN+ message.
389
+ #
390
+ # See #info for more information.
391
+ #
392
+ def warn(progname = nil, &block)
393
+ add(WARN, nil, progname, &block)
394
+ end
395
+
396
+ #
397
+ # Log an +ERROR+ message.
398
+ #
399
+ # See #info for more information.
400
+ #
401
+ def error(progname = nil, &block)
402
+ add(ERROR, nil, progname, &block)
403
+ end
404
+
405
+ #
406
+ # Log a +FATAL+ message.
407
+ #
408
+ # See #info for more information.
409
+ #
410
+ def fatal(progname = nil, &block)
411
+ add(FATAL, nil, progname, &block)
412
+ end
413
+
414
+ #
415
+ # Log an +UNKNOWN+ message. This will be printed no matter what the logger
416
+ # level.
417
+ #
418
+ # See #info for more information.
419
+ #
420
+ def unknown(progname = nil, &block)
421
+ add(UNKNOWN, nil, progname, &block)
422
+ end
423
+
424
+ #
425
+ # Close the logging device.
426
+ #
427
+ def close
428
+ @logdev.close if @logdev
429
+ end
430
+
431
+ private
432
+
433
+ # Severity label for logging. (max 5 char)
434
+ SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY)
435
+
436
+ def format_severity(severity)
437
+ SEV_LABEL[severity] || 'ANY'
438
+ end
439
+
440
+ def format_message(severity, datetime, progname, msg)
441
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
442
+ end
443
+
444
+
445
+ class Formatter
446
+ Format = "%s %s %s: %s [%d]\n"
447
+
448
+ attr_accessor :datetime_format
449
+
450
+ def initialize
451
+ @datetime_format = nil
452
+ end
453
+
454
+ def call(severity, time, progname, msg)
455
+ Format % [format_datetime(time), severity, progname, msg2str(msg), $$]
456
+ end
457
+
458
+ private
459
+
460
+ def format_datetime(time)
461
+ if @datetime_format.nil?
462
+ time.strftime("%Y-%m-%d %H:%M:%S") % time.usec
463
+ else
464
+ time.strftime(@datetime_format)
465
+ end
466
+ end
467
+
468
+ def msg2str(msg)
469
+ case msg
470
+ when ::String
471
+ msg
472
+ when ::Exception
473
+ "#{ msg.message } (#{ msg.class })\n" <<
474
+ (msg.backtrace || []).join("\n")
475
+ else
476
+ msg.inspect
477
+ end
478
+ end
479
+ end
480
+
481
+
482
+ class LogDevice
483
+ attr_reader :dev
484
+ attr_reader :filename
485
+
486
+ class LogDeviceMutex
487
+ include MonitorMixin
488
+ end
489
+
490
+ def initialize(log = nil, opt = {})
491
+ @dev = @filename = @shift_age = @shift_size = nil
492
+ @mutex = LogDeviceMutex.new
493
+ @filename = log
494
+ @shift_age = opt[:shift_age] || 7
495
+ @shift_size = opt[:shift_size] || 1048576
496
+ end
497
+
498
+ def write(message)
499
+ @mutex.synchronize do
500
+ @dev = open_logfile @filename
501
+ if @shift_age and @dev.respond_to?(:stat)
502
+ begin
503
+ check_shift_log
504
+ rescue
505
+ raise Logger::ShiftingError.new("Shifting failed. #{$!}")
506
+ end
507
+ end
508
+ @dev.write(message)
509
+ @dev.close
510
+ end
511
+ end
512
+
513
+ def close
514
+ @mutex.synchronize do
515
+ @dev.close
516
+ end
517
+ end
518
+
519
+ private
520
+
521
+ def open_logfile(filename)
522
+ if (FileTest.exist?(filename))
523
+ open(filename, (File::WRONLY | File::APPEND))
524
+ else
525
+ create_logfile(filename)
526
+ end
527
+ end
528
+
529
+ def create_logfile(filename)
530
+ logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
531
+ logdev.sync = true
532
+ add_log_header(logdev)
533
+ logdev
534
+ end
535
+
536
+ def add_log_header(file)
537
+ file.write(
538
+ "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
539
+ )
540
+ end
541
+
542
+ SiD = 24 * 60 * 60
543
+
544
+ def check_shift_log
545
+ if @shift_age.is_a?(Integer)
546
+ # Note: always returns false if '0'.
547
+ if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
548
+ shift_log_age
549
+ end
550
+ else
551
+ now = Time.now
552
+ if @dev.stat.mtime <= previous_period_end(now)
553
+ shift_log_period(now)
554
+ end
555
+ end
556
+ end
557
+
558
+ def shift_log_age
559
+ (@shift_age-3).downto(0) do |i|
560
+ if FileTest.exist?("#{@filename}.#{i}")
561
+ File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
562
+ end
563
+ end
564
+ @dev.close
565
+ File.rename("#{@filename}", "#{@filename}.0")
566
+ @dev = create_logfile(@filename)
567
+ return true
568
+ end
569
+
570
+ def shift_log_period(now)
571
+ postfix = previous_period_end(now).strftime("%Y%m%d") # YYYYMMDD
572
+ age_file = "#{@filename}.#{postfix}"
573
+ if FileTest.exist?(age_file)
574
+ raise RuntimeError.new("'#{ age_file }' already exists.")
575
+ end
576
+ @dev.close
577
+ File.rename("#{@filename}", age_file)
578
+ @dev = create_logfile(@filename)
579
+ return true
580
+ end
581
+
582
+ def previous_period_end(now)
583
+ case @shift_age
584
+ when /^daily$/
585
+ eod(now - 1 * SiD)
586
+ when /^weekly$/
587
+ eod(now - ((now.wday + 1) * SiD))
588
+ when /^monthly$/
589
+ eod(now - now.mday * SiD)
590
+ else
591
+ now
592
+ end
593
+ end
594
+
595
+ def eod(t)
596
+ Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
597
+ end
598
+ end
599
+
600
+
601
+ #
602
+ # == Description
603
+ #
604
+ # Application -- Add logging support to your application.
605
+ #
606
+ # == Usage
607
+ #
608
+ # 1. Define your application class as a sub-class of this class.
609
+ # 2. Override 'run' method in your class to do many things.
610
+ # 3. Instantiate it and invoke 'start'.
611
+ #
612
+ # == Example
613
+ #
614
+ # class FooApp < Application
615
+ # def initialize(foo_app, application_specific, arguments)
616
+ # super('FooApp') # Name of the application.
617
+ # end
618
+ #
619
+ # def run
620
+ # ...
621
+ # log(WARN, 'warning', 'my_method1')
622
+ # ...
623
+ # @log.error('my_method2') { 'Error!' }
624
+ # ...
625
+ # end
626
+ # end
627
+ #
628
+ # status = FooApp.new(....).start
629
+ #
630
+ class Application
631
+ include Logger::Severity
632
+
633
+ attr_reader :appname
634
+ attr_reader :logdev
635
+
636
+ #
637
+ # == Synopsis
638
+ #
639
+ # Application.new(appname = '')
640
+ #
641
+ # == Args
642
+ #
643
+ # +appname+:: Name of the application.
644
+ #
645
+ # == Description
646
+ #
647
+ # Create an instance. Log device is +STDERR+ by default. This can be
648
+ # changed with #set_log.
649
+ #
650
+ def initialize(appname = nil)
651
+ @appname = appname
652
+ @log = Logger.new(STDERR)
653
+ @log.progname = @appname
654
+ @level = @log.level
655
+ end
656
+
657
+ #
658
+ # Start the application. Return the status code.
659
+ #
660
+ def start
661
+ status = -1
662
+ begin
663
+ log(INFO, "Start of #{ @appname }.")
664
+ status = run
665
+ rescue
666
+ log(FATAL, "Detected an exception. Stopping ... #{$!} (#{$!.class})\n" << $@.join("\n"))
667
+ ensure
668
+ log(INFO, "End of #{ @appname }. (status: #{ status.to_s })")
669
+ end
670
+ status
671
+ end
672
+
673
+ #
674
+ # Sets the log device for this application. See the class Logger for an
675
+ # explanation of the arguments.
676
+ #
677
+ def set_log(logdev, shift_age = 0, shift_size = 1024000)
678
+ @log = Logger.new(logdev, shift_age, shift_size)
679
+ @log.progname = @appname
680
+ @log.level = @level
681
+ end
682
+
683
+ def log=(logdev)
684
+ set_log(logdev)
685
+ end
686
+
687
+ #
688
+ # Set the logging threshold, just like <tt>Logger#level=</tt>.
689
+ #
690
+ def level=(level)
691
+ @level = level
692
+ @log.level = @level
693
+ end
694
+
695
+ #
696
+ # See Logger#add. This application's +appname+ is used.
697
+ #
698
+ def log(severity, message = nil, &block)
699
+ @log.add(severity, message, @appname, &block) if @log
700
+ end
701
+
702
+ private
703
+
704
+ def run
705
+ raise RuntimeError.new('Method run must be defined in the derived class.')
706
+ end
707
+ end
708
+ end
709
+
710
+ end