dataMetaDom 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ %w(fileutils set).each { |r| require r }
4
+
5
+ module DataMetaDom
6
+
7
+ # doc target for javadocs
8
+ JAVA_DOC_TARGET = :java
9
+
10
+ # doc target for plaintext
11
+ PLAIN_DOC_TARGET = :plain
12
+
13
+ # All documentation targets
14
+ DOC_TARGETS = Set.new [PLAIN_DOC_TARGET, JAVA_DOC_TARGET]
15
+
16
+ =begin rdoc
17
+ Documentation tag
18
+
19
+ For command line details either check the new method's source or the README.rdoc file, the usage section.
20
+ =end
21
+ class Doc
22
+ =begin rdoc
23
+ Documentation target such as PLAIN_DOC_TARGET or JAVA_DOC_TARGET or whatever is added in the future.
24
+ May stick with plaintext unless it becomes easy to write in a common markup format and generate
25
+ specific doc format from it.
26
+
27
+ Can be one of the following:
28
+ * PLAIN_DOC_TARGET - +plain+
29
+ * JAVA_DOC_TARGET - +java+
30
+ =end
31
+ attr_reader :target
32
+
33
+ =begin rdoc
34
+ The text of the documentation.
35
+ =end
36
+ attr_accessor :text
37
+
38
+ =begin rdoc
39
+ Creates an instance for the given target and given text.
40
+ =end
41
+ def initialize(target, text)
42
+ @target = target.to_sym
43
+ #noinspection RubyArgCount
44
+ raise "Unsupported docs target #@target" unless DOC_TARGETS.member?(@target)
45
+ @text = text
46
+ end
47
+
48
+ =begin rdoc
49
+ Parses the documentation from the given source, returns an instance of Doc.
50
+
51
+ * Parameters:
52
+ * +source+ - an instance of SourceFile
53
+ * +params+ - an array, first member is the target.
54
+ =end
55
+ def self.parse(source, params)
56
+ text = ''
57
+ while (line = source.nextLine(true))
58
+ case line
59
+ when /^\s*#{END_KW}\s*$/
60
+ retVal = Doc.new params[0], text
61
+ return retVal
62
+ else
63
+ text << line
64
+ end # case
65
+ end # while line
66
+ raise "Parsing a doc: missing end keyword, source=#{source}"
67
+ end
68
+
69
+ # Textual for the instance
70
+ def to_s; "Doc-#{target}\n#{text}" end
71
+ end # class Doc
72
+
73
+ # Anything that can have documentation.
74
+ class Documentable
75
+
76
+ =begin rdoc
77
+ The hash keyed by the target.
78
+ =end
79
+ attr_accessor :docs
80
+
81
+ #Initializes the instance with an empty hash.
82
+ def initialize; @docs = {} end
83
+
84
+ # Fetches the instance of Doc by the given key, the target
85
+ def getDoc(key); @docs[key] end
86
+
87
+ # Adds the document by putting it into the underlying cache with the target key.
88
+ def addDoc(doc)
89
+ @docs[doc.target] = doc
90
+ end
91
+
92
+ # All the ids, namely the targets of all the documents on this instance.
93
+ def ids; @docs.keys end
94
+
95
+ # All the instances of the Doc stored on this instance.
96
+ def all; @docs.values end
97
+
98
+ =begin rdoc
99
+ Determines if the given document target is defined on this instance.
100
+ * Parameter:
101
+ * +key+ - the target.
102
+ =end
103
+ def has?(key) ; @docs.member?(key) end
104
+
105
+ =begin rdoc
106
+ Reinitializes the instance with no docs.
107
+ =end
108
+ def clear; @docs[] = {} end
109
+
110
+ =begin rdoc
111
+ Attempts to consume a Doc from the given source, returns true if succeeded.
112
+ * Parameters:
113
+ * +source+ - an instance of SourceFile
114
+ * +target+ - the target, the format of the Doc.
115
+ =end
116
+ def docConsumed?(source)
117
+ source.line =~ /^\s*#{DOC}\s+(\w+)$/ ? addDoc(Doc.parse(source, [$1])) : nil
118
+ end
119
+
120
+ end # class Docs
121
+
122
+ end
@@ -0,0 +1,125 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ %w(fileutils set dataMetaDom/docs dataMetaDom/ver).each { |r| require r }
4
+
5
+ module DataMetaDom
6
+
7
+ =begin rdoc
8
+ Worded enum, enumeration expressed as a list of words, each word consisting of alphanumerical symbols only.
9
+
10
+ For command line details either check the new method's source or the README.rdoc file, the usage section.
11
+ =end
12
+ class Enum < VerDoccable
13
+ =begin rdoc
14
+ The full name of the enum, including namespace if any
15
+ =end
16
+ attr_reader :name
17
+
18
+ =begin rdoc
19
+ Attempts to consume/parse an enum from the given source on the given model, returns it if succeeds, returns +nil+ otherwise.
20
+ * Parameters
21
+ * model - an instance of Model
22
+ * src - an instance of SourceFile
23
+ =end
24
+ def self.consumed?(model, src)
25
+ src.line =~ /^\s*#{ENUM}\s+(\w+)$/ ? model.addEnum(Enum.new(combineNsBase(
26
+ nsAdjustment(src.namespace, model.options, src), $1)).parse(src)) : nil
27
+ end
28
+
29
+ =begin rdoc
30
+ Creates an instance for the given name, initializes internal variables.
31
+ =end
32
+ def initialize(name); @name = name.to_sym; @map = {}; @format = nil; @counter = -1 end
33
+
34
+ # Keyword for this entity - +enum+ literally.
35
+ def sourceKeyWord; ENUM end
36
+
37
+ =begin rdoc
38
+ Adds a word to the given enum - use judiciously, when building an Enum from memory.
39
+ To parse DataMeta DOM source, use the consumed? method.
40
+ =end
41
+ def addKey(word)
42
+ raise "Duplicate value '#{word}' in the enum '#@name'" if (@map.key?(word))
43
+ @counter += 1
44
+ @map[word] = @counter # ordinal
45
+ end
46
+
47
+ =begin rdoc
48
+ Parses the keys from the current line on the given source, yields one key at a time.
49
+ Used by the parse method.
50
+ * Parameter:
51
+ * +source+ - the instance of the SourceFile.
52
+ =end
53
+ def getKeys(source)
54
+ newVals = source.line.split(/[\s,]+/)
55
+ puts newVals.join("|") if $DEBUG
56
+ newVals.each { |v|
57
+
58
+ raise "Invalid worded enum '#{v}', must start with #{ID_START}" \
59
+ ", line ##{source.lineNum}" unless v =~ /^#{ID_START}\w*$/
60
+ yield v.to_sym
61
+ }
62
+ end
63
+
64
+ =begin rdoc
65
+ Returns the ordinal enum value for the given word.
66
+ =end
67
+ def ordinal(word); @map[word] end
68
+
69
+ =begin rdoc
70
+ Opposite to ordinal, for the given ordinal returns the word.
71
+ =end
72
+ def [](ord); @map.invert[ord] end
73
+
74
+ =begin rdoc
75
+ All words defined on this enum, sorted alphabetically, *not* by enum order.
76
+ =end
77
+ def values; @map.keys.sort end
78
+
79
+ # Raw values, in the order in which they were inserted
80
+ def rawVals; @map.keys end
81
+
82
+ =begin
83
+ Determines if this enum is equal or an extention of the other. Extension means, it contains the whole other
84
+ enum plus more members at the tail.
85
+
86
+ Returns: +:ne+ if neither equal nor extension, +:eq+ if equal, +:xt+ if extension.
87
+ =end
88
+ def isEqOrXtOf(other)
89
+ return :eq if @map.keys == other.rawVals
90
+ other.rawVals.each_index{ |x|
91
+ return :ne if @map.keys[x] != other.rawVals[x]
92
+ }
93
+ :xt
94
+ end
95
+
96
+ # All ordinals on this enum, sorted as integer values.
97
+ def keys; @map.values.sort end
98
+
99
+ # Textual representation of this enum - a list of words separated by commma.
100
+ def to_s; "Enum #{@name}(#{@map.keys.join(', ')}, ver=#{self.ver})" end
101
+
102
+ =begin rdoc
103
+ Parses the given source into current instance. See the consumed? method for the details.
104
+ * Parameter:
105
+ * +source+ - the instance of SourceFile.
106
+ =end
107
+ def parse(source)
108
+ while (line = source.nextLine)
109
+
110
+ case line
111
+ when /^\s*#{END_KW}\s*$/
112
+ self.ver = source.ver unless self.ver
113
+ raise "Version missing for the enum #{name}" unless self.ver
114
+ self.docs = source.docs.clone if source.docs
115
+ source.docs.clear
116
+ return self
117
+ else
118
+ getKeys(source) { |k| addKey k }
119
+ end # case
120
+ end # while line
121
+ self
122
+ end # def parse
123
+ end #class Enum
124
+
125
+ end
@@ -0,0 +1,182 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'dataMetaDom/docs'
4
+ require 'dataMetaDom/ver'
5
+
6
+ module DataMetaDom
7
+
8
+ =begin rdoc
9
+ A field for a Record, with the full name including namespace if any, required flag, DataType and
10
+ default value if any.
11
+
12
+ For command line details either check the new method's source or the README.rdoc file, the usage section.
13
+ =end
14
+ class Field < VerDoccable
15
+
16
+ # Aggregation type: Set
17
+ SET = 'set'
18
+ # Aggregation type: List
19
+ LIST = 'list'
20
+ # Aggregation type: Deque
21
+ DEQUE = 'deque'
22
+ # Making a hash/set of 3 members is outright silly, use simple array:
23
+ AGGRS = [SET, LIST, DEQUE]
24
+ # Map keyword, that's a different kind of aggregator with 2 types
25
+ MAP = 'map'
26
+
27
+ =begin rdoc
28
+ The name for this field, full name including namespace if any.
29
+ =end
30
+ attr_reader :name
31
+
32
+ =begin rdoc
33
+ Required flag, true for a required field, false for an optional one.
34
+ =end
35
+ attr_accessor :isRequired
36
+
37
+ =begin rdoc
38
+ Default value for this field if any.
39
+ =end
40
+ attr_accessor :default
41
+
42
+ =begin rdoc
43
+ An instance of DataType for the given field. For a Map -- source type
44
+ =end
45
+ attr_accessor :dataType
46
+
47
+ =begin rdoc
48
+ Aggregate indicator - one of the constants: SET, LIST, DEQUE. Note it does not include MAP.
49
+ =end
50
+ attr_accessor :aggr
51
+
52
+ =begin rdoc
53
+ Target type for a Map
54
+ =end
55
+ attr_accessor :trgType
56
+
57
+ =begin rdoc
58
+ +matches+ Regular expression specification if any, for a string
59
+ =end
60
+ attr_accessor :regex
61
+
62
+ class << self # static members of the class
63
+ =begin rdoc
64
+ Parses a new field from the given source, adds to the given record with the source info.
65
+ * Parameters:
66
+ * +source+ - the instance of SourceFile to parse from
67
+ * +record+ - the instance of the Record to which the newly parsed Field will be added.
68
+ =end
69
+ def consume(model, source, record)
70
+ newField = Field.new.parse(model, source)
71
+ if record.docs
72
+ newField.docs = record.docs.clone
73
+ record.docs.clear
74
+ end
75
+ record.addField newField, model, source.snapshot
76
+ end
77
+
78
+ =begin rdoc
79
+ Creates a new field with the the given name, type and required flag. Use to build a Model from the code.
80
+ * Parameters:
81
+ * +name+ - full name for the new field, including namespace if any.
82
+ * +dataType+ - an instance of DataType, can use reusable types defined on util.rb.
83
+ * +req+ - the required flag, true for required, see isRequired for details.
84
+ =end
85
+ def create(name, dataType, req=false)
86
+ raise ArgumentError, 'Must specify name and type when creating a field from the code' if !name || !dataType
87
+ result = Field.new name
88
+ result.dataType = dataType
89
+ result.isRequired = req
90
+ result
91
+ end
92
+ end
93
+
94
+ # For readability - determine if the field is aggregated type: LIST, DEQUE, SET
95
+ def aggr?; defined?(@aggr) && @aggr != nil; end
96
+ # For readability - determine if the field is a map
97
+ def map?; defined?(@trgType) && @trgType != nil; end
98
+ def set?; defined?(@aggr) && @aggr == SET; end
99
+
100
+
101
+ =begin rdoc
102
+ Another way ot create a Field from the code - a constructor, with the given name if any.
103
+ See the class method create for more details.
104
+ =end
105
+ def initialize(name=nil)
106
+ super()
107
+ @length = nil
108
+ if name
109
+ raise ArgumentError, %<Invlalid field name "#{name}", must be alphanum starting with alpha> unless name =~ /^#{ID_START}\w*$/
110
+ @name = name.to_sym
111
+ end
112
+ @default = nil
113
+ end
114
+
115
+ =begin rdoc
116
+ Parses this field from the given source.
117
+ * Parameter:
118
+ * +source+ - an instance of SourceFile
119
+ =end
120
+ def parse(model, source)
121
+ @aggr = nil
122
+ @trgType = nil
123
+ src = nil
124
+
125
+ if source.line =~ /^\s*([#{REQUIRED_PFX}#{OPTIONAL_PFX}])\s*#{MAP}\{\s*([^\s,]+)\s*,\s*([^\}]+)}(.+)/
126
+ # is it a map{} ?
127
+ ro, srcSpec, trgSpec, tail = $1, $2, $3, $4
128
+ src = %|#{ro}#{srcSpec}#{tail ? tail : ''}|
129
+ @trgType = DataType.parse(source, trgSpec)
130
+ unless STANDARD_TYPES.member?(@trgType.type)
131
+ ns, base = DataMetaDom.splitNameSpace(@trgType.type)
132
+ newNs = nsAdjustment(ns, model.options, source)
133
+ newNsVer = "#{newNs}.#{base}".to_sym
134
+ @trgType.type = newNsVer # adjust the type for the map target type too
135
+ end
136
+ # elsif source.line =~ /^\s*([#{REQUIRED_PFX}#{OPTIONAL_PFX}])\s*(string)\s+(#{ID_START}\w*)\s*(.+)?$/
137
+ # is it a string with no length?
138
+ # req, typeSpec, name, tail = $1, $2, $3, $4
139
+ # src = %|#{req}#{typeSpec}[0] #{name} #{tail}|
140
+ else # is it a list, deque or set?
141
+ AGGRS.each { |a| ## aggregates do not allow matching nor they allow defaults
142
+ if source.line =~ /^\s*([#{REQUIRED_PFX}#{OPTIONAL_PFX}])\s*#{a}\{([^\}]+)\}(.+)/
143
+ @aggr = a
144
+ src = %|#{$1}#{$2}#{$3}|
145
+ break
146
+ end
147
+ }
148
+ end
149
+ r = (src ? src : source.line).scan(/^\s*([#{REQUIRED_PFX}#{OPTIONAL_PFX}])\s*(\S+)\s+(#{ID_START}\w*)\s*(.+)?$/)
150
+ raise "Invalid field spec '#{line}'" unless r
151
+ req, typeSpec, name, tail = r[0]
152
+ defaultSpec = tail =~ /(=\S.+)/ ? $1.strip : nil
153
+ @regex = tail =~ /#{MATCHES}\s+(.+)/ ? $1.strip : nil # regex can have any symbols, even space, but it starts with a non-space
154
+ #puts "<#{line}> <#{req}> <#{typeSpec}> <#{dimSpec}> <#@name>"
155
+ raise 'Invalid field spec format' if !name || name.empty? || !req || !typeSpec
156
+ @name = name.to_sym
157
+ @dataType = DataType.parse(source, typeSpec)
158
+ @default = defaultSpec[1..-1] if defaultSpec # skip the = sign
159
+ @isRequired = req.to_sym == REQUIRED_PFX
160
+ self
161
+ end
162
+
163
+ =begin rdoc
164
+ Specification of a default value per the DataMeta DOM syntax or empty string if there is no default value on this field.
165
+ =end
166
+ def default_spec; @default ? "=#{@default}" : '' end
167
+ # matches specification
168
+ def matches_spec; @regex ? " matches #{@regex}" : '' end
169
+
170
+ =begin rdoc
171
+ Required flag as per the DataMeta DOM syntax, either DataMetaDom::REQUIRED_PFX or DataMetaDom::OPTIONAL_PFX,
172
+ the constants defined in the util.rb
173
+ =end
174
+ def req_spec; @isRequired ? REQUIRED_PFX : OPTIONAL_PFX end
175
+
176
+ =begin rdoc
177
+ Textual representation of all aspects of the field.
178
+ =end
179
+ def to_s; "Field #{@name}(#{req_spec}#{@dataType})#{@default ? '=<' + @default + '>' : ''}" end
180
+ end
181
+
182
+ end
@@ -0,0 +1,41 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ module DataMetaDom
4
+
5
+ =begin rdoc
6
+ Helper method for runnables.
7
+
8
+ Prints help and exits placing the purpose of the runnable and the parameters description in proper spots.
9
+
10
+ Exits with code 0 if errorText is +nil+, exits with code 1 otherwise. Prints errorText with
11
+ proper dressing to the +STDERR+.
12
+ =end
13
+ def help(file, purpose, params, errorText = nil)
14
+ puts <<HELP
15
+ DataMeta DOM version #{DataMetaDom::VERSION}
16
+
17
+ #{purpose}. Usage: #{File.basename(file)} #{params}
18
+
19
+ HELP
20
+
21
+ $stderr.puts "\nERROR: #{errorText}" if errorText
22
+ exit errorText ? 0 : 1
23
+ end
24
+
25
+ # Shortcut to help for the Pojo Generator.
26
+ def helpPojoGen(file, errorText=nil)
27
+ help(file, 'POJO generator', '<DataMeta DOM source> <target directory>', errorText)
28
+ end
29
+
30
+ # Shortcut to help for the MySQL DDL Generator.
31
+ def helpMySqlDdl(file, errorText=nil)
32
+ help(file, 'MySQL DDL generator', '<DataMeta DOM source> <target directory>', errorText)
33
+ end
34
+
35
+ # Shortcut to help for the Oracle DDL Generator.
36
+ def helpOracleDdl(file, errorText=nil)
37
+ help(file, 'Oracle DDL generator', '<DataMeta DOM source> <target directory>', errorText)
38
+ end
39
+
40
+ module_function :help, :helpPojoGen, :helpMySqlDdl
41
+ end