dataMetaDom 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/History.md +6 -0
- data/PostInstall.txt +1 -0
- data/README.md +137 -0
- data/Rakefile +13 -0
- data/bin/dataMetaGvExport.rb +97 -0
- data/bin/dataMetaMySqlDdl.rb +27 -0
- data/bin/dataMetaOracleDdl.rb +26 -0
- data/bin/dataMetaPojo.rb +27 -0
- data/bin/dataMetaReVersion.rb +66 -0
- data/bin/dataMetaSameFullJ.rb +13 -0
- data/bin/dataMetaSameIdJ.rb +12 -0
- data/lib/dataMetaDom/converter.rb +70 -0
- data/lib/dataMetaDom/dataType.rb +112 -0
- data/lib/dataMetaDom/docs.rb +122 -0
- data/lib/dataMetaDom/enum.rb +125 -0
- data/lib/dataMetaDom/field.rb +182 -0
- data/lib/dataMetaDom/help.rb +41 -0
- data/lib/dataMetaDom/model.rb +274 -0
- data/lib/dataMetaDom/mySql.rb +256 -0
- data/lib/dataMetaDom/ora.rb +295 -0
- data/lib/dataMetaDom/pojo.rb +1056 -0
- data/lib/dataMetaDom/python.rb +168 -0
- data/lib/dataMetaDom/recAttr.rb +271 -0
- data/lib/dataMetaDom/record.rb +397 -0
- data/lib/dataMetaDom/ref.rb +127 -0
- data/lib/dataMetaDom/sourceFile.rb +150 -0
- data/lib/dataMetaDom/sources.rb +68 -0
- data/lib/dataMetaDom/util.rb +279 -0
- data/lib/dataMetaDom/ver.rb +244 -0
- data/lib/dataMetaDom.rb +141 -0
- data/test/test_dataMetaDom.rb +130 -0
- data/test/test_helper.rb +6 -0
- data/tmpl/java/migrationEntityEnums.erb +172 -0
- data/tmpl/python/entity.erb +50 -0
- metadata +126 -0
@@ -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
|