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,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