copland 0.8.0 → 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.
Files changed (50) hide show
  1. data/doc/manual-html/chapter-1.html +227 -36
  2. data/doc/manual-html/chapter-10.html +155 -82
  3. data/doc/manual-html/chapter-11.html +90 -267
  4. data/doc/manual-html/chapter-12.html +289 -71
  5. data/doc/manual-html/chapter-13.html +430 -0
  6. data/doc/manual-html/chapter-2.html +45 -21
  7. data/doc/manual-html/chapter-3.html +45 -21
  8. data/doc/manual-html/chapter-4.html +45 -21
  9. data/doc/manual-html/chapter-5.html +45 -21
  10. data/doc/manual-html/chapter-6.html +49 -21
  11. data/doc/manual-html/chapter-7.html +45 -21
  12. data/doc/manual-html/chapter-8.html +66 -26
  13. data/doc/manual-html/chapter-9.html +48 -24
  14. data/doc/manual-html/index.html +54 -22
  15. data/doc/manual-html/manual.css +12 -0
  16. data/doc/manual-html/tutorial-1.html +45 -21
  17. data/doc/manual-html/tutorial-2.html +45 -21
  18. data/doc/manual-html/tutorial-3.html +45 -21
  19. data/doc/manual-html/tutorial-4.html +45 -21
  20. data/doc/manual-html/tutorial-5.html +45 -21
  21. data/doc/manual/manual.css +12 -0
  22. data/doc/manual/manual.rb +1 -1
  23. data/doc/manual/manual.yml +426 -20
  24. data/doc/packages/copland.html +41 -9
  25. data/doc/packages/copland.lib.html +36 -8
  26. data/doc/packages/copland.remote.html +46 -10
  27. data/doc/packages/copland.webrick.html +16 -65
  28. data/doc/packages/index.html +1 -1
  29. data/doc/presentation/copland.mgp +1083 -0
  30. data/doc/presentation/to_html.rb +52 -0
  31. data/lib/copland/configuration-point/common.rb +32 -1
  32. data/lib/copland/configuration/yaml/service-point.rb +10 -1
  33. data/lib/copland/log-factory.rb +28 -12
  34. data/lib/copland/logger.rb +155 -0
  35. data/lib/copland/models/singleton.rb +8 -2
  36. data/lib/copland/package.rb +32 -14
  37. data/lib/copland/service-point.rb +7 -0
  38. data/lib/copland/thread.rb +104 -0
  39. data/lib/copland/utils.rb +10 -3
  40. data/lib/copland/version.rb +2 -2
  41. data/test/configuration/yaml/tc_service-point-processor.rb +8 -0
  42. data/test/custom-logger.yml +2 -1
  43. data/test/impl/tc_logging-interceptor.rb +12 -12
  44. data/test/logger.yml +1 -1
  45. data/test/mock.rb +2 -0
  46. data/test/tc_logger.rb +19 -6
  47. data/test/tc_package.rb +25 -0
  48. data/test/tc_queryable-mutex.rb +75 -0
  49. data/test/tc_registry.rb +8 -4
  50. metadata +9 -2
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'redcloth'
5
+
6
+ $target_dir = "html"
7
+ $resolution = "800x600"
8
+ $presentation = "copland2.mgp"
9
+ $image_format = "png"
10
+
11
+ FileUtils.rm_rf $target_dir
12
+ FileUtils.mkdir_p $target_dir
13
+ system "mgp -g #{$resolution} -D #{$target_dir} -E #{$image_format} #{$presentation}"
14
+
15
+ puts "======================================================="
16
+ puts "inserting slide notes into generated HTML..."
17
+
18
+ content = File.read( $presentation )
19
+ pages = content.split( /%page/ ).map { |p| p.strip }
20
+ head = pages.shift
21
+
22
+ pages.each_with_index do |page, index|
23
+ if page =~ /^%%==*\n(.*)\n%%==*\n/m
24
+ note = $1.gsub( /^%% ?/, "" ).strip
25
+
26
+ if note.length > 0
27
+ slide_file = File.join( $target_dir, "mgp%05d.html" % (index+1) )
28
+ slide_html = File.read( slide_file )
29
+
30
+ note.gsub!( /(\S)\n([ \t]*\w)/, '\1 \2' )
31
+
32
+ if slide_html =~ /<IMG SRC=\"mgp.*png\".*><BR>/
33
+ before = $` + $&
34
+ after = $'
35
+ new_html = before + "\n" +
36
+ "<style type=\"text/css\">" +
37
+ "#notes h1 { font-size: large }\n" +
38
+ "#notes h2 { font-size: normal }\n" +
39
+ "#notes h3 { font-size: normal }\n" +
40
+ "</style>" +
41
+ "<h2>Slide Notes:</h2>" +
42
+ "<blockquote id=\"notes\">" +
43
+ RedCloth.new(note).to_html +
44
+ "</blockquote>" +
45
+ after
46
+ puts " writing slide #{index+1}"
47
+ File.open( slide_file, "w" ) { |file| file.write new_html }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ puts "done!"
@@ -156,10 +156,11 @@ module Copland
156
156
  # for this configuration point; or if no schema exists, run the
157
157
  # values through the Copland::translate_values method.
158
158
  def process_values( values )
159
+ Thread.current[:disable_symbol_substitution] = true
159
160
  s = schema
160
161
  contributor = contributor_of( values )
161
162
  if s.respond_to?( :process )
162
- if s.is_a?( Array )
163
+ if values.is_a?( Array )
163
164
  values = values.map! { |value|
164
165
  s.process( self, contributor, value )
165
166
  }
@@ -172,7 +173,37 @@ module Copland
172
173
  end
173
174
 
174
175
  return values
176
+ ensure
177
+ Thread.current[:disable_symbol_substitution] = false
178
+ end
179
+
180
+ # Processes all substitution symbols in the values of this configuration
181
+ # point, in place. This has a guard to ensure that it is only executed
182
+ # once.
183
+ def substitute_symbols!
184
+ Thread.critical = true
185
+ return if @substitution_processed
186
+ @substitution_processed = true
187
+ substitute_symbols( self )
188
+ ensure
189
+ Thread.critical = false
190
+ end
191
+
192
+ # The recursive portion of the symbol substitution step. This looks for
193
+ # substitution symbols in all of the values of the configuration point,
194
+ # substituting them as they are found.
195
+ def substitute_symbols( value )
196
+ case value
197
+ when Hash
198
+ value.each_pair { |k,v| value[k] = substitute_symbols( v ) }
199
+ when Array
200
+ value.map! { |v| substitute_symbols( v ) }
201
+ when String
202
+ Copland::substitute_symbols( owner.registry, value )
203
+ end
204
+ value
175
205
  end
206
+ private :substitute_symbols
176
207
 
177
208
  # Returns false. Once a configuration point is fixated, it will include
178
209
  # the Copland::ConfigurationPoint::ConfigurationPointFunctionality::Fixated
@@ -62,7 +62,8 @@ module Copland
62
62
 
63
63
  # The list of valid keys that may exist on the input.
64
64
  VALID_KEYS = [ "description", "model", "implementor",
65
- "interceptors", "listen-to", "schema" ]
65
+ "interceptors", "listen-to", "schema",
66
+ "visibility" ]
66
67
 
67
68
  # The list of required keys that must exist on the input.
68
69
  REQUIRED_KEYS = [ "implementor" ]
@@ -101,6 +102,14 @@ module Copland
101
102
  when "schema"
102
103
  point.schema = @schema.process( point, value )
103
104
 
105
+ when "visibility"
106
+ point.visibility = case value.downcase
107
+ when "public" then :public
108
+ when "private" then :private
109
+ else raise ParserError,
110
+ "invalid visibility value #{value.inspect}"
111
+ end
112
+
104
113
  else
105
114
  raise CoplandBug, "[BUG] invalid key discovered too late: #{key.inspect}"
106
115
  end
@@ -31,7 +31,7 @@
31
31
  # =============================================================================
32
32
  #++
33
33
 
34
- require 'logger'
34
+ require 'copland/logger'
35
35
  require 'thread'
36
36
  require 'yaml'
37
37
 
@@ -51,6 +51,9 @@ module Copland
51
51
  # The default name of the log file to write to.
52
52
  DEFAULT_LOG_FILENAME = "./copland.log"
53
53
 
54
+ # The default format of the log messages (see Logger for more info)
55
+ DEFAULT_MESSAGE_FORMAT = "[%-5p] %d -- %C: %m"
56
+
54
57
  # Translate names of levels to their actual values.
55
58
  LEVEL_TRANSLATOR = {
56
59
  "DEBUG" => Logger::DEBUG,
@@ -61,8 +64,11 @@ module Copland
61
64
  "UNKNOWN" => Logger::UNKNOWN
62
65
  }
63
66
 
64
- # The default (date) format string to use when logging.
65
- attr_reader :default_format
67
+ # The default date format string to use when logging.
68
+ attr_reader :default_date_format
69
+
70
+ # The default message format string to use when logging.
71
+ attr_reader :default_message_format
66
72
 
67
73
  # The default log level to use for logs that are created.
68
74
  attr_reader :default_level
@@ -82,11 +88,14 @@ module Copland
82
88
  # rolled.
83
89
  # * <tt>:roll_frequency</tt>: either 'daily', 'weekly', or 'monthly'.
84
90
  # * <tt>:roll_size</tt>: the maximum size of a log file.
85
- # * <tt>:default_format</tt>: the default date format string for the log.
91
+ # * <tt>:default_date_format</tt>: the default date format string for the
92
+ # log.
93
+ # * <tt>:default_message_format</tt>: the default message format string for
94
+ # the log.
86
95
  # * <tt>:default_level</tt>: the default log level for the log.
87
96
  # * <tt>:levels</tt>: a hash of patterns that map to a hash of 'level'
88
- # and 'format' keys, specifying the log level and date format for any
89
- # log whose name matches the key.
97
+ # 'date_format', and 'message_format' keys, specifying the log level,
98
+ # date format, and message format for any log whose name matches the key.
90
99
  def initialize( opts={} )
91
100
  opts[ :config_file ] ||= DEFAULT_CONFIG_FILE
92
101
  read_config opts
@@ -102,10 +111,13 @@ module Copland
102
111
  :shift_size => roll_size )
103
112
  end
104
113
 
105
- @default_format = opts[ :default_format ]
114
+ @default_date_format = opts[ :default_date_format ]
115
+ @default_message_format = opts[ :default_message_format ] ||
116
+ DEFAULT_MESSAGE_FORMAT
106
117
  @default_level = opts[ :default_level ]
107
118
 
108
- @levels = Hash.new "level" => nil, "format" => nil
119
+ @levels = Hash.new "level" => nil, "date-format" => nil,
120
+ "message-format" => nil
109
121
 
110
122
  ( opts[ :levels ] || Hash.new ).each_pair do |key, value|
111
123
  key = Regexp.new( "^" + key.gsub( /\./, "\\." ).gsub( /\*/, ".*" ) )
@@ -125,7 +137,8 @@ module Copland
125
137
  file = opts[ :config_file ]
126
138
  if File.exist?( file ) && File.readable?( file )
127
139
  config = YAML.load( File.read( file ) )
128
- opts[ :default_format ] ||= config[ "default-format" ]
140
+ opts[ :default_date_format ] ||= config[ "default-date-format" ]
141
+ opts[ :default_message_format ] ||= config[ "default-message-format" ]
129
142
  opts[ :default_level ] ||=
130
143
  LEVEL_TRANSLATOR[ config[ "default-level" ] ] ||
131
144
  config[ "default-level" ]
@@ -169,14 +182,17 @@ module Copland
169
182
  definition = find_definition( name )
170
183
 
171
184
  level = definition[ "level" ] || @default_level
172
- format = definition[ "format" ] || @default_format
185
+ date_format = definition[ "date-format" ] || @default_date_format
186
+ message_format = definition[ "message-format" ] ||
187
+ @default_message_format
173
188
 
174
189
  level = LEVEL_TRANSLATOR[ level ] || level
175
190
 
176
- logger = Logger.new( @device )
191
+ logger = Copland::Logger.new( @device )
177
192
  logger.level = level if level
178
- logger.datetime_format = format if format
179
193
  logger.progname = name
194
+ logger.datetime_format = date_format if date_format
195
+ logger.message_format = message_format if message_format
180
196
 
181
197
  @loggers[ name ] = logger
182
198
  return logger
@@ -0,0 +1,155 @@
1
+ #--
2
+ # =============================================================================
3
+ # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # * Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ #
12
+ # * Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # * The names of its contributors may not be used to endorse or promote
17
+ # products derived from this software without specific prior written
18
+ # permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ # =============================================================================
32
+ #++
33
+
34
+ require 'logger'
35
+ require 'strscan'
36
+ require 'copland/errors'
37
+
38
+ module Copland
39
+
40
+ # A specialization of the standard Logger class that comes with Ruby. This
41
+ # provides the additional functionality of a fully-customizable message
42
+ # format, whereas the original only provided a customizable date format.
43
+ class Logger < ::Logger
44
+
45
+ # The brief name of this logger (derived from #progname).
46
+ attr_reader :name
47
+
48
+ # The format string for the message (+nil+ if the default should be used)
49
+ attr_reader :message_format
50
+
51
+ # The map of specifier options supported by this class.
52
+ SPECIFIER_OPTIONS = {
53
+ "c" => { :type => "s", :value => "@name" },
54
+ "C" => { :type => "s", :value => "self.progname" },
55
+ "d" => { :type => "s", :value => "opts[:timestamp]" },
56
+ "F" => { :type => "s", :value => "opts[:caller_file]" },
57
+ "l" => { :type => "s", :value => "opts[:caller_info]" },
58
+ "L" => { :type => "d", :value => "opts[:caller_line]" },
59
+ "m" => { :type => "s", :value => "opts[:msg]" },
60
+ "M" => { :type => "s", :value => "opts[:caller_method]" },
61
+ "n" => { :type => "s", :value => "$/" },
62
+ "p" => { :type => "s", :value => "opts[:severity]" },
63
+ "t" => { :type => "d", :value => "Thread.current.__id__" },
64
+ "%" => { :type => "s", :value => "'%'" },
65
+ "P" => { :type => "s", :value => "opts[:progname]" },
66
+ "$" => { :type => "d", :value => "$$" }
67
+ }
68
+
69
+ # The regular expression for matching specifier patterns in the
70
+ # format strings.
71
+ SPECIFIER_PATTERN = /(.*?)%(-?\d*(?:\.\d+)?)?([cCdFlLmMnpt%$P])/
72
+
73
+ # Extracts the unqualified name from the progname, after setting the
74
+ # progname.
75
+ def progname=( progname )
76
+ super
77
+ @name = progname.split( /\./ ).last
78
+ end
79
+
80
+ # Set the message format string to the given string. This also pre-parses
81
+ # the format for faster processing.
82
+ #
83
+ # The format string is a printf-formatted string, which supports the following
84
+ # format specifiers:
85
+ #
86
+ # c:: the unqualified name of the logger
87
+ # C:: the fully-qualified name of the logger
88
+ # d:: the date/time string (as formatted by the #datetime_format string)
89
+ # F:: the filename of the calling routine
90
+ # l:: the location of the calling routine
91
+ # L:: the line number of the calling routine
92
+ # m:: the message to log
93
+ # M:: the name of the calling method
94
+ # n:: the newline character
95
+ # p:: the name of the priority (or severity) used to log this method
96
+ # t:: the id of the current thread
97
+ # %:: a percentage character
98
+ # P:: the progname that was passed to the logger method
99
+ # $:: the current process id
100
+ def message_format=( format )
101
+ @message_format = format
102
+ return unless format
103
+
104
+ @needs_caller_info = false
105
+
106
+ format_string = ""
107
+ format_parameters = []
108
+
109
+ @message_format_tokens = []
110
+ format.scan( SPECIFIER_PATTERN ) do |v|
111
+ format_string << v[0] if v[0].length > 0
112
+ opts = SPECIFIER_OPTIONS[ v[2] ]
113
+ format_string << "%#{v[1]}#{opts[:type]}"
114
+ format_parameters << opts[:value]
115
+ @needs_caller_info = true if v[2] =~ /[FlLM]/
116
+ end
117
+ format_string << $' if $'.length > 0
118
+ format_string << "\n"
119
+
120
+ definition = "proc { |opts| #{format_string.inspect} % [ #{format_parameters.join(',')} ] }"
121
+ @message_formatter = eval( definition )
122
+ end
123
+
124
+ # Format the message using the given parameters. If a message format has
125
+ # not been given, just call the superclass's implementation. Otherwise,
126
+ # process the tokens from the parsed message format.
127
+ def format_message( severity, timestamp, msg, progname )
128
+ if @message_format.nil?
129
+ super
130
+ else
131
+ opts = {
132
+ :severity => severity,
133
+ :timestamp => timestamp,
134
+ :msg => msg,
135
+ :progname => progname
136
+ }
137
+
138
+ if @needs_caller_info
139
+ stack = caller
140
+ stack.shift while stack.first =~ /\blogger\.rb/
141
+ opts[:caller_info] = caller_info = stack.first
142
+ match = caller_info.match( /(.*):(\d+)(?::in `(.*)')?/ )
143
+ opts[:caller_file] = match[1]
144
+ opts[:caller_line] = match[2]
145
+ opts[:caller_method] = match[3]
146
+ end
147
+
148
+ @message_formatter.call( opts )
149
+ end
150
+ end
151
+ private :format_message
152
+
153
+ end
154
+
155
+ end
@@ -31,8 +31,9 @@
31
31
  # =============================================================================
32
32
  #++
33
33
 
34
+ require 'copland/errors'
34
35
  require 'copland/models/abstract'
35
- require 'thread'
36
+ require 'copland/thread'
36
37
 
37
38
  module Copland
38
39
 
@@ -47,7 +48,7 @@ module Copland
47
48
  # Create a new SingletonServiceModel on the given service point.
48
49
  def initialize( service_point )
49
50
  super
50
- @mutex = Mutex.new
51
+ @mutex = QueryableMutex.new
51
52
  end
52
53
 
53
54
  # Return the singleton instance of the service point. This is
@@ -55,6 +56,11 @@ module Copland
55
56
  def instance( &init )
56
57
  return @instance if @instance
57
58
 
59
+ if @mutex.self_locked?
60
+ raise CoplandException,
61
+ "circular dependency on #{service_point.full_name} detected"
62
+ end
63
+
58
64
  @mutex.synchronize do
59
65
  return @instance if @instance
60
66
  new_instance( &init )
@@ -78,14 +78,24 @@ module Copland
78
78
  end
79
79
 
80
80
  # Returns the service point with the given name. If no such service point
81
- # exists, this returns +nil+.
82
- def service_point( name )
83
- @service_points[ name ]
81
+ # exists, this returns +nil+. If +include_private+ is false, then this will
82
+ # also return +nil+ if the point exists, but is marked private.
83
+ def service_point( name, include_private=false )
84
+ point = @service_points[ name ]
85
+ return nil if point.nil? || point.visibility == :private &&
86
+ !include_private
87
+ point
84
88
  end
85
89
 
86
- # This returns the names of all service points in the package.
87
- def service_points
88
- @service_points.keys.freeze
90
+ # This returns the names of all service points in the package. If
91
+ # +include_private+ is true (the default), then only the public service
92
+ # points will be returned.
93
+ def service_points( include_private=false )
94
+ names = @service_points.keys.dup
95
+ if !include_private
96
+ names.reject! { |p| @service_points[p].visibility != :public }
97
+ end
98
+ names
89
99
  end
90
100
 
91
101
  # This instantiates the service point with the given name and returns the
@@ -95,15 +105,19 @@ module Copland
95
105
  # If a block is given, it will be used to initialize the service (but only
96
106
  # when the service is created--if the service is a singleton service and
97
107
  # it was created previously, the init block will be ignored).
98
- def service( name, &init )
99
- point = service_point( name ) or raise ServicePointNotFound, name
108
+ #
109
+ # If +include_private+ is true, then only public service points may be
110
+ # instantiated.
111
+ def service( name, include_private=false, &init )
112
+ point = service_point( name, include_private ) or
113
+ raise ServicePointNotFound, name
100
114
  point.instance( &init )
101
115
  end
102
116
 
103
117
  # Returns +true+ if the named service exists in this package, and +false+
104
118
  # otherwise.
105
- def service_exist?( name )
106
- return !service_point( name ).nil?
119
+ def service_exist?( name, include_private=false )
120
+ return !service_point( name, include_private ).nil?
107
121
  end
108
122
 
109
123
  # This is a convenience method for returning a service with the given name,
@@ -155,8 +169,10 @@ module Copland
155
169
  end
156
170
 
157
171
  # Iterates over each service point in the package.
158
- def each_service_point( &block )
159
- @service_points.each_value( &block )
172
+ def each_service_point( include_private=false, &block )
173
+ @service_points.each_value do |pt|
174
+ yield pt if pt.visibility == :public || include_private
175
+ end
160
176
  self
161
177
  end
162
178
 
@@ -171,8 +187,10 @@ module Copland
171
187
  end
172
188
 
173
189
  # Returns the number of service points in the package.
174
- def service_point_count
175
- @service_points.size
190
+ def service_point_count( include_private=false )
191
+ @service_points.
192
+ reject { |k,v| v.visibility == :private && !include_private }.
193
+ size
176
194
  end
177
195
 
178
196
  # Returns the number of configuration points in the package.