dataMetaDom 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
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
+ =begin rdoc
8
+ All sources including all includes from the master file.
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 Sources
13
+
14
+ =begin rdoc
15
+ Start parsing from the master file, collect all the files that are included.
16
+ =end
17
+ def initialize(masterFile)
18
+ masterPath = File.dirname(masterFile)
19
+ @todo = {}; @done = {}
20
+ libSpec = ENV[DATAMETA_LIB]
21
+ @paths = libSpec ? libSpec.split(File::PATH_SEPARATOR).map { |e| uniPath(e) } : []
22
+ @paths.unshift(masterPath).flatten! if masterPath
23
+ @paths.unshift '.' # start looking in the current directory and then in the rest of the path
24
+ src = SourceFile.new(masterPath, File.basename(masterFile))
25
+ @todo[src.key] = src
26
+ end
27
+
28
+ =begin rdoc
29
+ Returns the set of the keys of the source files alredy parsed.
30
+ =end
31
+ def doneKeys; @done.keys end
32
+
33
+ =begin rdoc
34
+ Fetches the instance of SourceFile by its key.
35
+ =end
36
+ def [](key); @done[key] end
37
+
38
+ # Queue a source file for parsing
39
+ def queue(name)
40
+ # need to resolve the name to the path first
41
+ includeDir = nil
42
+ @paths.each { |m|
43
+ fullName = "#{m}#{File::SEPARATOR}#{name}"
44
+ if File.exist?(fullName)
45
+ includeDir = m
46
+ break
47
+ end
48
+ }
49
+ raise "Missing include '#{name}' in the path #{@paths.join(File::PATH_SEPARATOR)}" unless includeDir
50
+ src = SourceFile.new(includeDir, name)
51
+ @todo[src.key]=src unless @todo[src.key] || @done[src.key]
52
+ self
53
+ end
54
+
55
+ =begin rdoc
56
+ Returns next source file in queue if any, returns +nil+ if no more source files left to parse.
57
+ =end
58
+ def next
59
+ return nil if @todo.empty?
60
+ @key = nil
61
+ @todo.each_key { |k| @key = k; break }
62
+ @val = @todo[@key]
63
+ @todo.delete @key; @done[@key] = @val
64
+ @val
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,279 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'set'
4
+ require 'logger'
5
+
6
+ module DataMetaDom
7
+
8
+ # Logger set to WARN, daily rollover and max size 10M
9
+ # Feel free to change any of it.
10
+ L = Logger.new('dataMetaDom.log', 'daily', 10*1024*1024)
11
+ L.level = Logger::WARN
12
+
13
+ # Keyword: namespace
14
+ NAMESPACE = :namespace
15
+
16
+ # Keyword: include
17
+ INCLUDE = :include
18
+
19
+ # Keyword, data type: string with fixed length
20
+ CHAR = :char
21
+
22
+ # Keyword, data type: string with variable length
23
+ STRING = :string
24
+
25
+ # Keyword, data type: integer
26
+ INT = :int
27
+
28
+ # Keyword, data type: float (real numbers)
29
+ FLOAT = :float
30
+
31
+ # Keyword, data type: boolean
32
+ BOOL = :bool
33
+
34
+ # Keyword, data type: bitset
35
+ BITSET = :bitset
36
+
37
+ # Keyword, data type: URL
38
+ URL = :url
39
+
40
+ # Keyword, data type: map
41
+ MAPPING = :mapping
42
+
43
+ # Keyword, data type: datetime
44
+ DATETIME = :datetime
45
+
46
+ # Keyword, data type: numeric
47
+ NUMERIC = :numeric
48
+
49
+ # Keyword, identity
50
+ IDENTITY = :identity
51
+
52
+ # Keyword, unique
53
+ UNIQUE = :unique
54
+
55
+ # Keyword, matches
56
+ MATCHES = :matches
57
+
58
+ # Keyword, index
59
+ INDEX = :index
60
+
61
+ # Key to a no-namespace
62
+ NO_NAMESPACE = ''.to_sym
63
+
64
+ # Wiki for DataMeta DOM
65
+ WIKI = 'https://github.com/eBayDataMeta/DataMeta'
66
+
67
+ # HTML tag referencing the WIKI
68
+ WIKI_REF_HTML = "<a href='#{WIKI}'>DataMeta</a>"
69
+
70
+ # Keyword, data type, the RAW type refers to raw data, like a byte array
71
+ RAW = :raw
72
+
73
+ # the reference keyword was not a good idea, too much confusion and dupe functionality
74
+ # with the better way, namely referencing an object by name
75
+ #REFERENCE=:reference
76
+
77
+ =begin rdoc
78
+ Keyword +doc+, documentation.
79
+ =end
80
+ DOC = :doc
81
+
82
+ =begin rdoc
83
+ Keyword +ver+, version info.
84
+ =end
85
+ VER_KW = :ver
86
+
87
+ # Keyword, data type, enum
88
+ ENUM = :enum
89
+
90
+ # Keyword, end
91
+ END_KW = :end
92
+
93
+ # Keyword, record
94
+ RECORD = :record
95
+
96
+ # Package separator in a namespace.
97
+ PACK_SEPARATOR = '.'
98
+
99
+ # Environment variable for DataMeta DOM library path.
100
+ DATAMETA_LIB = 'DATAMETA_LIB'
101
+
102
+ # for source code generation, 2 spaces
103
+ SOURCE_INDENT = ' ' * 2
104
+
105
+ # Prefix for a required field
106
+ REQUIRED_PFX = '+'.to_sym
107
+
108
+ # Prefix for an optional field
109
+ OPTIONAL_PFX = '-'.to_sym
110
+
111
+ # DataMeta DOM standard types that have a dimension with a scale
112
+ SCALE_TYPES = Set.new [NUMERIC]
113
+
114
+ =begin rdoc
115
+ Data Types that must be dimensioned, with a length or with a length and a scale, as
116
+ it includes all the SCALE_TYPES too..
117
+ =end
118
+ DIMMED_TYPES = Set.new ([FLOAT, INT, CHAR, RAW] << SCALE_TYPES.to_a).flatten
119
+
120
+ # Optionally dimmable types - may have a dim or may have not
121
+ OPT_DIMMABLE = Set.new ([STRING]).flatten
122
+
123
+ # standard types is a superset of dimmensionable types, adding BOOL and DATETIME
124
+ STANDARD_TYPES = Set.new(([BOOL, DATETIME, URL] << DIMMED_TYPES.to_a << OPT_DIMMABLE.to_a << SCALE_TYPES.to_a).flatten)
125
+
126
+ =begin rdoc
127
+ Record attribute keywords:
128
+ * +identity+
129
+ * +unique+
130
+ * +index+
131
+ =end
132
+ REC_ATTR_KEYWORDS = Set.new [IDENTITY, UNIQUE, INDEX]
133
+ # Valid first symbol of a DataMeta DOM idenifier such as entity or enum name, field name, enum item.
134
+ ID_START = '[A-Za-z_]'
135
+
136
+ # Valid first symbol for a DataMeta DOM Type
137
+ TYPE_START = '[A-Z]'
138
+
139
+ puts "Standard types: #{STANDARD_TYPES.to_a.map { |k| k.to_s }.sort.join(', ')}" if $DEBUG
140
+
141
+ # Migration context - for a record
142
+ class MigrCtx
143
+ attr_accessor :rec, :canAuto, :isSkipped
144
+ def initialize(name)
145
+ @rec = name
146
+ @canAuto = true # assume we can automigrate unless encounter problems that would require a HIT
147
+ @isSkipped = false
148
+ end
149
+ end
150
+
151
+ =begin rdoc
152
+ Suffix for the java source files for the implementors of the DataMetaSame interface by all the fields on the class.
153
+ =end
154
+ SAME_FULL_SFX = '_DmSameFull'
155
+
156
+ =begin rdoc
157
+ Suffix for the java source files for the implementors of the DataMetaSame interface by identity field(s) on the class.
158
+ =end
159
+ SAME_ID_SFX = '_DmSameId'
160
+
161
+ # +DataMetaSame+ generation style: Full Compare, compare by all the fields defined in the class
162
+ FULL_COMPARE = :full
163
+
164
+ =begin rdoc
165
+ +DataMetaSame+ generation style: Compare by the identity fields only as defined on DataMetaDom::Record
166
+ =end
167
+ ID_ONLY_COMPARE = :id
168
+
169
+ # One indent step for java classes, spaces.
170
+ INDENT = ' ' * 4
171
+
172
+ # keep in sync with generated classes such as the Java class `CannedRegexUtil` in DataMeta DOM Core/Java etc.
173
+ CANNED_RX = Set.new [:email, :phone]
174
+
175
+ # holds a custom regex symbol and the variables that use this regex
176
+ class RegExEntry
177
+ attr_reader :r, :vars, :req
178
+ # initializes interna variables
179
+ def initialize(regex, var, req)
180
+ @r = regex
181
+ @vars = Set.new [var]
182
+ @req = req
183
+ end
184
+ # adds the variable to the instance
185
+ def <<(var)
186
+ @vars << var
187
+ end
188
+
189
+ def req?; @req end
190
+ end
191
+
192
+ # Registry for the regexes so we don't repeat those
193
+ class RegExRoster
194
+ attr_reader :i_to_r, :r_to_i, :canned
195
+
196
+ class << self
197
+ # Converts the given custom RegEx index to the matching Pattern static final variable name
198
+ def ixToVarName(index)
199
+ "REGEX___#{index}___"
200
+ end
201
+ end
202
+
203
+ # sets index to 0, initializes hashes
204
+ def initialize
205
+ @index = 0
206
+ @i_to_r = {}
207
+ @r_to_i = {}
208
+ @canned = {}
209
+ end
210
+
211
+ # adds a new regex to the registry
212
+ def register(f)
213
+ var = f.name
214
+ rx = f.regex
215
+ rx = rx[1..-2] if rx.length > 2 && rx.start_with?('/') && rx.end_with?('/')
216
+ k = rx.to_sym
217
+ if CANNED_RX.member?(k)
218
+ if @canned.has_key?(k)
219
+ @canned[k] << var
220
+ else
221
+ @canned[k] = RegExEntry.new(k, var, f.isRequired)
222
+ end
223
+ elsif @r_to_i.has_key?(k)
224
+ # this regex is already registered, just add the variable
225
+ @i_to_r[@index] << var
226
+ else
227
+ @index += 1
228
+ @i_to_r[@index] = RegExEntry.new(k, var, f.isRequired)
229
+ @r_to_i[k] = @index
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+ =begin rdoc
236
+ With the given full type including the namespace if any and the given namespace (java package, python package etc),
237
+ figures out whether the full type has to be reference in full, if it belongs to a different namespace,
238
+ or just by the base name if it belongs to the same package.
239
+
240
+ * Parameters
241
+ * +fullType+ - full data type including the namespace if any
242
+ * +namespace+ - reference namespace.
243
+
244
+ For example, passed:
245
+ "com.acme.proj.Klass", "com.acme.lib"
246
+ will return
247
+ "com.acme.proj.Klass"
248
+
249
+ but when passed:
250
+ "com.acme.proj.Klass", "com.acme.proj"
251
+ will return
252
+ "Klass"
253
+
254
+ This is to avoid excessive verbosity when referencing entities in the same package.
255
+ =end
256
+ def condenseType(fullType, ref_namespace)
257
+ ns, base = DataMetaDom.splitNameSpace(fullType)
258
+ # noinspection RubyNestedTernaryOperatorsInspection
259
+ DataMetaDom.validNs?(ns, base) ? ( ns == ref_namespace ? base : fullType) : fullType
260
+ end
261
+
262
+ # Migrator implementor name
263
+ def migrClass(base, ver1, ver2); "Migrate_#{base}_v#{ver1.toVarName}_to_v#{ver2.toVarName}" end
264
+
265
+ =begin rdoc
266
+ Builds and returns the Java-style getter name for the given field. This style is used in other platforms such as
267
+ Python, for consistency.
268
+ =end
269
+ def getterName(f); "get#{DataMetaXtra::Str.capFirst(f.name.to_s)}" end
270
+
271
+ =begin rdoc
272
+ Builds and returns the Java-setter setter name for the given field. This style is used in other platforms such as
273
+ Python, for consistency.
274
+ =end
275
+ def setterName(f); "set#{DataMetaXtra::Str.capFirst(f.name.to_s)}" end
276
+
277
+ module_function :setterName, :getterName
278
+
279
+ end
@@ -0,0 +1,244 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'fileutils'
4
+ require 'typesafe_enum'
5
+ require 'set'
6
+ require 'dataMetaDom/docs'
7
+
8
+ module DataMetaDom
9
+
10
+ =begin
11
+ Semantic Version implementation.
12
+
13
+ See {http://semver.org this page} for details
14
+ =end
15
+ class SemVer
16
+ attr_reader :source, :semanticPartsOnly, :items
17
+ # Version difference levels as an enum
18
+ class DiffLevel < TypesafeEnum::Base
19
+ new :NONE # Versions are equal
20
+ new :MAJOR # Difference in the Major part
21
+ new :MINOR # Difference in the Minor part
22
+ new :UPDATE # Difference in the Update (Patch) part
23
+ new :BUILD # Difference in the Build part
24
+ end
25
+
26
+ # Split by dots pattern
27
+ DOTS_SPLIT = %r<\.>
28
+ # Any Integral part of the Version - just digits
29
+ DIGITS = %r<^[0-9]+$>
30
+
31
+ # Major part index in the @items array
32
+ MAJOR_INDEX = 0
33
+ # Minor part index in the @items array
34
+ MINOR_INDEX = MAJOR_INDEX + 1
35
+ # Update part index in the @items array
36
+ UPDATE_INDEX = MINOR_INDEX + 1
37
+ # Build part index in the @items array
38
+ BUILD_INDEX = UPDATE_INDEX + 1
39
+ # Minimal size of the @items array
40
+ ITEMS_MIN_SIZE = UPDATE_INDEX + 1
41
+ # Max size of the @items array
42
+ ITEMS_MAX_SIZE = BUILD_INDEX + 1
43
+
44
+ # Equality for the saucer operator <=>
45
+ EQ = 0
46
+ # Strictly "greater than" for the saucer operator <=>
47
+ GT = 1
48
+ # Strictly "lesser than" for the saucer operator <=>
49
+ LT = -1
50
+
51
+ # Parsing constructor
52
+ def initialize(src)
53
+ raise ArgumentError, "Attempted to create an instance of #{self.class.name} from a nil" if src.nil?
54
+ @source = src
55
+ # put everything in an array -- this provides free eql? and hash() methods
56
+ @items = []
57
+ src.split(DOTS_SPLIT).each { |i|
58
+ if i =~ DIGITS
59
+ @items << i.to_i
60
+ else
61
+ break
62
+ end
63
+ }
64
+ raise ArgumentError, %<Invalid semantic version format: #{src}> if items.size < ITEMS_MIN_SIZE ||
65
+ items.size > ITEMS_MAX_SIZE
66
+
67
+ raise ArgumentError,
68
+ %<Invalid semantic version format: "#{src}": build version can not be zero.> unless build.nil? ||
69
+ build != 0
70
+
71
+ @semanticPartsOnly = @items.map{|i| i.to_s}.join('.')
72
+
73
+ end
74
+
75
+ # Major part of the version
76
+ def major; @items[MAJOR_INDEX] end
77
+
78
+ # Minor part of the version
79
+ def minor; @items[MINOR_INDEX] end
80
+
81
+ # Update part of the version
82
+ def update; @items[UPDATE_INDEX] end
83
+
84
+ # Build part of the version or nil
85
+ def build; items.size > BUILD_INDEX ? @items[BUILD_INDEX] : nil end
86
+
87
+ # Difference Level, computes one of the DiffLevel values
88
+ def diffLevel(other)
89
+ return DiffLevel::MAJOR if major != other.major
90
+ return DiffLevel::MINOR if minor != other.minor
91
+ return DiffLevel::UPDATE if update != other.update
92
+ if (
93
+ !build.nil? && !other.build.nil? && (build <=> other.build) != EQ
94
+ ) || (
95
+ build.nil? && !other.build.nil?
96
+ ) || ( !build.nil? && other.build.nil? )
97
+ return DiffLevel::BUILD
98
+ end
99
+ DiffLevel::NONE
100
+ end
101
+
102
+ # Override the eql? for the == operator to work
103
+ def eql?(o); @items == o.items end
104
+
105
+ # Override the hash() method for the sets and maps to work
106
+ def hash; @items.hash end
107
+
108
+ # The Saucer Operator, Ruby equivalent of Java's compareTo(...)
109
+ def <=>(o)
110
+ raise ArgumentError, %<Attempt to compare #{self.class.name} "#{self}" to a nil> if o.nil?
111
+
112
+ 0.upto(UPDATE_INDEX) { |x|
113
+ cmp = items[x] <=> o.items[x]
114
+ return cmp unless cmp == EQ # not equal: end of the story, that's the comparison result
115
+ }
116
+ # if we are here, the Minor, Major and the Update are equal. See what's up with the build if any:
117
+
118
+ # this object is newer (version bigger) because it has a build number but the other does not
119
+ return GT if items.size > o.items.size
120
+
121
+ # this object is older (version lesser) because it does not have a build number but the other does
122
+ return LT if items.size < o.items.size
123
+
124
+ # We got build part in self and the other, return the build part comparison:
125
+ build <=> o.build
126
+ end
127
+
128
+ # For a simple string representation, just show the source
129
+ def to_s; @source end
130
+
131
+ # Long string for debugging and detailed logging - shows semantic parts as parsed
132
+ def to_long_s; "#{self.class.name}{#{@source}(#{@semanticPartsOnly})}" end
133
+
134
+ =begin
135
+ Consistently and reproducibly convert the version specs to the text suitable for making it a part of a class name or a
136
+ variable name
137
+ =end
138
+ def toVarName; @items.join('_') end
139
+
140
+ # Overload the equals operator
141
+ def ==(other); self.<=>(other) == EQ end
142
+ # Overload the greater-than operator
143
+ def >(other); self.<=>(other) == GT end
144
+ # Overload the lesser-than operator
145
+ def <(other); self.<=>(other) == LT end
146
+ # Overload the greater-than-or-equals operator
147
+ def >=(other); self.<=>(other) >= EQ end
148
+ # Overload the lesser-than-or-equals operator
149
+ def <=(other); self.<=>(other) <= EQ end
150
+
151
+ class << self
152
+ # Builds an instance of SemVer from the given specs whatever they are
153
+ def fromSpecs(specs)
154
+ case specs
155
+ when SemVer
156
+ specs
157
+ when String
158
+ SemVer.new(specs)
159
+ else
160
+ raise ArgumentError, %<Unsupported SemVer specs type #{specs}==#{specs.inspect}>
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ =begin rdoc
167
+ Version info.
168
+ =end
169
+ class Ver
170
+ =begin rdoc
171
+ Full version info.
172
+ =end
173
+ attr_accessor :full
174
+
175
+ =begin rdoc
176
+ Creates an instance with the given full version.
177
+ =end
178
+ def initialize(specs)
179
+ @full = if specs.kind_of?(Integer)
180
+ raise ArgumentError,
181
+ %|Invalid version specs: "#{specs
182
+ }"; a version must be of a valid Semantic format|
183
+ else
184
+ SemVer.fromSpecs(specs)
185
+ end
186
+ end
187
+
188
+ class << self
189
+ # Reversions all the files in the given paths recursively
190
+ def reVersion(path, namespace, globs, srcVer, trgVer)
191
+ vPat = srcVer ? srcVer.toVarName : '\d+_\d+_\d+'
192
+ globs.each { |g|
193
+ Dir.glob("#{path}/#{g}").each { |f|
194
+ origLines = IO.read(f).split("\n")
195
+ newLines = []
196
+ origLines.each { |line|
197
+ newLines << (line.end_with?('KEEP') ? line :
198
+ line.gsub(%r~#{namespace.gsub(/\./, '\.')}\.v#{vPat}~, "#{namespace}.v#{trgVer.toVarName}"))
199
+ }
200
+ IO.write(f, newLines.join("\n"), mode: 'wb')
201
+ }
202
+ }
203
+ Dir.entries(path).select{|e| File.directory?(File.join(path, e))}.reject{|e| e.start_with?('.')}.each {|d|
204
+ reVersion File.join(path, d), namespace, globs, srcVer, trgVer
205
+ }
206
+
207
+ end
208
+
209
+ end
210
+
211
+ =begin rdoc
212
+ Textual presentation for the instance.
213
+ =end
214
+ def to_s; "ver #{full}" end
215
+ end
216
+
217
+ =begin rdoc
218
+ Anything having a version. It must be also documentable, but not everything documentable is also versionable,
219
+ like, for example, Record Field or an Enum part.
220
+ =end
221
+ class VerDoccable < Documentable
222
+ =begin rdoc
223
+ The version info, an instance of Ver.
224
+ =end
225
+ attr_accessor :ver
226
+ =begin rdoc
227
+ Resets stateful information on the entity level, like docs that should not apply to the next entity if missing.
228
+ =end
229
+ def resetEntity
230
+ docs.clear
231
+ end
232
+
233
+ =begin rdoc
234
+ Attempts to parse an instance of Ver from the current line on the given instance of SourceFile.
235
+ Returns the instance of Ver if successful, nil otherwise.
236
+ Parameter:
237
+ * +src+ - the instance of SourceFile to parse the version info from.
238
+ =end
239
+ def self.verConsumed?(src)
240
+ src.line =~ /^\s*#{VER_KW}\s+(\S+)\s*$/ ? Ver.new($1) : nil
241
+ end
242
+
243
+ end
244
+ end