dataMetaDom 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.
@@ -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