power_stencil 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +194 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/bin/update_plugin_template +79 -0
  13. data/doc/builds.md +267 -0
  14. data/doc/entities.md +964 -0
  15. data/doc/example_use_cases.md +59 -0
  16. data/doc/images/power-stencil-architecture.svg +151 -0
  17. data/doc/images/power-stencil-entity-build.svg +481 -0
  18. data/doc/images/power-stencil-entity-creation.svg +375 -0
  19. data/doc/images/power-stencil-project-overview.svg +2534 -0
  20. data/doc/images/power-stencil-simple-flow.svg +265 -0
  21. data/doc/plugins.md +169 -0
  22. data/doc/templates.md +332 -0
  23. data/etc/base_commands_definition.yml +259 -0
  24. data/etc/meta_templates/plugin_seed/etc/command_line.yaml +12 -0
  25. data/etc/meta_templates/plugin_seed/etc/plugin_config.yaml +4 -0
  26. data/etc/meta_templates/plugin_seed/etc/templates/.git_keep +0 -0
  27. data/etc/meta_templates/plugin_seed/lib/{entity}.rb.erb +34 -0
  28. data/etc/meta_templates/plugin_seed/lib/{entity}/entity_definitions/{entity}_entity.rb.erb +11 -0
  29. data/etc/meta_templates/plugin_seed/lib/{entity}/plugin_helper.rb.erb +17 -0
  30. data/etc/meta_templates/plugin_seed/lib/{entity}/version.rb.erb +3 -0
  31. data/etc/meta_templates/plugin_seed/lib/{entity}/{entity}_processor.rb.erb +19 -0
  32. data/etc/meta_templates/plugin_seed/psplugin_{entity}.gemspec +36 -0
  33. data/etc/power_stencil.yaml +92 -0
  34. data/etc/templates/plugin_definition/.gitignore +11 -0
  35. data/etc/templates/plugin_definition/.rspec +3 -0
  36. data/etc/templates/plugin_definition/.travis.yml +5 -0
  37. data/etc/templates/plugin_definition/CODE_OF_CONDUCT.md +74 -0
  38. data/etc/templates/plugin_definition/Gemfile +6 -0
  39. data/etc/templates/plugin_definition/LICENSE.txt +21 -0
  40. data/etc/templates/plugin_definition/README.md +43 -0
  41. data/etc/templates/plugin_definition/Rakefile +6 -0
  42. data/etc/templates/plugin_definition/bin/console +14 -0
  43. data/etc/templates/plugin_definition/bin/setup +8 -0
  44. data/etc/templates/plugin_definition/etc/command_line.yaml +12 -0
  45. data/etc/templates/plugin_definition/etc/plugin_config.yaml +4 -0
  46. data/etc/templates/plugin_definition/etc/templates/.git_keep +1 -0
  47. data/etc/templates/plugin_definition/lib/{entity}.rb.erb +34 -0
  48. data/etc/templates/plugin_definition/lib/{entity}/entity_definitions/{entity}_entity.rb.erb +11 -0
  49. data/etc/templates/plugin_definition/lib/{entity}/plugin_helper.rb.erb +17 -0
  50. data/etc/templates/plugin_definition/lib/{entity}/version.rb.erb +3 -0
  51. data/etc/templates/plugin_definition/lib/{entity}/{entity}_processor.rb.erb +19 -0
  52. data/etc/templates/plugin_definition/psplugin_{entity}.gemspec +36 -0
  53. data/etc/templates/plugin_definition/spec/spec_helper.rb +14 -0
  54. data/etc/templates/plugin_definition/spec/{entity}_spec.rb +9 -0
  55. data/etc/templates/project/.copy_ignore +2 -0
  56. data/etc/templates/project/.gitignore.erb +6 -0
  57. data/etc/templates/project/.ps_project/entities/.gitkeep +0 -0
  58. data/etc/templates/project/.ps_project/entities/README.md +11 -0
  59. data/etc/templates/project/.ps_project/entity_definitions/.gitkeep +0 -0
  60. data/etc/templates/project/.ps_project/entity_definitions/README.md +15 -0
  61. data/etc/templates/project/.ps_project/personal-config.yaml +8 -0
  62. data/etc/templates/project/.ps_project/plugins/.gitkeep +0 -0
  63. data/etc/templates/project/.ps_project/templates-templates/.gitkeep +0 -0
  64. data/etc/templates/project/.ps_project/templates-templates/README.md +13 -0
  65. data/etc/templates/project/.ps_project/user_entities/.gitkeep +0 -0
  66. data/etc/templates/project/.ps_project/user_entities/README.md +12 -0
  67. data/etc/templates/project/.ps_project/versioned-config.yaml +6 -0
  68. data/etc/templates/simple_exec/main.sh +4 -0
  69. data/exe/power_stencil +28 -0
  70. data/lib/power_stencil.rb +53 -0
  71. data/lib/power_stencil/command_processors/build.rb +43 -0
  72. data/lib/power_stencil/command_processors/check.rb +35 -0
  73. data/lib/power_stencil/command_processors/create.rb +70 -0
  74. data/lib/power_stencil/command_processors/delete.rb +38 -0
  75. data/lib/power_stencil/command_processors/edit.rb +35 -0
  76. data/lib/power_stencil/command_processors/entity_helper.rb +105 -0
  77. data/lib/power_stencil/command_processors/get.rb +22 -0
  78. data/lib/power_stencil/command_processors/info.rb +41 -0
  79. data/lib/power_stencil/command_processors/init.rb +47 -0
  80. data/lib/power_stencil/command_processors/new_plugin.rb +31 -0
  81. data/lib/power_stencil/command_processors/root.rb +27 -0
  82. data/lib/power_stencil/command_processors/shell.rb +37 -0
  83. data/lib/power_stencil/command_processors/trace_helper.rb +20 -0
  84. data/lib/power_stencil/dsl/base.rb +24 -0
  85. data/lib/power_stencil/dsl/entities.rb +46 -0
  86. data/lib/power_stencil/dsl/plugin_generation.rb +17 -0
  87. data/lib/power_stencil/engine/base.rb +50 -0
  88. data/lib/power_stencil/engine/build_handling.rb +77 -0
  89. data/lib/power_stencil/engine/directory_processor.rb +111 -0
  90. data/lib/power_stencil/engine/entities_definitions.rb +42 -0
  91. data/lib/power_stencil/engine/entities_handling.rb +76 -0
  92. data/lib/power_stencil/engine/entity_engine.rb +20 -0
  93. data/lib/power_stencil/engine/init_engine.rb +18 -0
  94. data/lib/power_stencil/engine/project_engine.rb +75 -0
  95. data/lib/power_stencil/engine/renderers/erb.rb +21 -0
  96. data/lib/power_stencil/error.rb +20 -0
  97. data/lib/power_stencil/initializer.rb +87 -0
  98. data/lib/power_stencil/plugins/base.rb +54 -0
  99. data/lib/power_stencil/plugins/capabilities.rb +30 -0
  100. data/lib/power_stencil/plugins/command_line.rb +27 -0
  101. data/lib/power_stencil/plugins/config.rb +32 -0
  102. data/lib/power_stencil/plugins/dependencies.rb +32 -0
  103. data/lib/power_stencil/plugins/gem.rb +57 -0
  104. data/lib/power_stencil/plugins/require.rb +77 -0
  105. data/lib/power_stencil/plugins/templates.rb +21 -0
  106. data/lib/power_stencil/project/base.rb +79 -0
  107. data/lib/power_stencil/project/config.rb +54 -0
  108. data/lib/power_stencil/project/create.rb +30 -0
  109. data/lib/power_stencil/project/info.rb +72 -0
  110. data/lib/power_stencil/project/paths.rb +119 -0
  111. data/lib/power_stencil/project/plugins.rb +89 -0
  112. data/lib/power_stencil/project/proxy.rb +13 -0
  113. data/lib/power_stencil/project/templates.rb +56 -0
  114. data/lib/power_stencil/project/versioning.rb +29 -0
  115. data/lib/power_stencil/system_entity_definitions/all.rb +14 -0
  116. data/lib/power_stencil/system_entity_definitions/buildable.rb +23 -0
  117. data/lib/power_stencil/system_entity_definitions/entity_override.rb +8 -0
  118. data/lib/power_stencil/system_entity_definitions/entity_project_common.rb +33 -0
  119. data/lib/power_stencil/system_entity_definitions/has_associated_files.rb +13 -0
  120. data/lib/power_stencil/system_entity_definitions/non_persistent.rb +14 -0
  121. data/lib/power_stencil/system_entity_definitions/plugin.rb +17 -0
  122. data/lib/power_stencil/system_entity_definitions/process_descriptor.rb +15 -0
  123. data/lib/power_stencil/system_entity_definitions/project_config.rb +24 -0
  124. data/lib/power_stencil/system_entity_definitions/project_entity.rb +16 -0
  125. data/lib/power_stencil/system_entity_definitions/simple_exec.rb +47 -0
  126. data/lib/power_stencil/utils/directory_processor.rb +54 -0
  127. data/lib/power_stencil/utils/file_edit.rb +87 -0
  128. data/lib/power_stencil/utils/file_helper.rb +56 -0
  129. data/lib/power_stencil/utils/gem_utils.rb +13 -0
  130. data/lib/power_stencil/utils/secure_require.rb +26 -0
  131. data/lib/power_stencil/utils/semantic_version.rb +128 -0
  132. data/lib/power_stencil/version.rb +3 -0
  133. data/power_stencil.gemspec +32 -0
  134. metadata +287 -0
@@ -0,0 +1,8 @@
1
+ class UniverseCompiler::Entity::Override
2
+
3
+ extend PowerStencil::SystemEntityDefinitions::Buildable
4
+ include PowerStencil::SystemEntityDefinitions::EntityProjectCommon
5
+
6
+ field :description
7
+
8
+ end
@@ -0,0 +1,33 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ module EntityProjectCommon
5
+
6
+ def save(uri = source_uri, force_files_generation: false)
7
+ super(uri)
8
+ unless PowerStencil.project.entity_type_templates[type].nil?
9
+ PowerStencil.project.generate_entity_dir_for_entity self, force: force_files_generation
10
+ end
11
+ self
12
+ end
13
+
14
+ def delete(force_files_deletion: false)
15
+ unless PowerStencil.project.entity_type_templates[type].nil?
16
+ PowerStencil.project.delete_entity_dir_for_entity self, force: force_files_deletion
17
+ end
18
+ super()
19
+ self
20
+ end
21
+
22
+ def buildable_by
23
+ self.class.buildable_by
24
+ end
25
+
26
+ def buildable?
27
+ self.class.buildable?
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ module HasAssociatedFiles
5
+
6
+ def templates_path
7
+ File.join PowerStencil.project.project_root, type.to_s, name
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ module NonPersistent
5
+
6
+ def save(uri = source_uri)
7
+ PowerStencil.logger.debug "Won't save '#{self.class.to_s}' because defined as non persistent"
8
+ self
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ class Plugin < PowerStencil::SystemEntityDefinitions::ProjectEntity
5
+
6
+ include PowerStencil::SystemEntityDefinitions::NonPersistent
7
+
8
+ DOC = 'A system class not to be used'
9
+
10
+ entity_type :plugin_definition
11
+
12
+ end
13
+
14
+
15
+ end
16
+ end
17
+
@@ -0,0 +1,15 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ class ProcessDescriptor < PowerStencil::SystemEntityDefinitions::ProjectEntity
5
+
6
+ DOC = 'Generic description of a process'
7
+
8
+ entity_type :process_descriptor
9
+
10
+ field :process, :not_null
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ class ProjectConfig < PowerStencil::SystemEntityDefinitions::ProjectEntity
5
+
6
+ include PowerStencil::SystemEntityDefinitions::NonPersistent
7
+
8
+ DOC = 'A system singleton read-only entity holding runtime PowerStencil configuration'
9
+
10
+ entity_type :project_config
11
+
12
+ field_reader :simulate, :debug
13
+
14
+ def initialize(fields: {}, universe: nil)
15
+ super
16
+ self.name = 'Project Config'
17
+ end
18
+
19
+ end
20
+
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,16 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ class ProjectEntity < UniverseCompiler::Entity::Base
5
+
6
+ extend PowerStencil::SystemEntityDefinitions::Buildable
7
+ include PowerStencil::SystemEntityDefinitions::EntityProjectCommon
8
+
9
+ entity_type :base_entity
10
+
11
+ field :description
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ module PowerStencil
2
+ module SystemEntityDefinitions
3
+
4
+ class SimpleExec < PowerStencil::SystemEntityDefinitions::ProjectEntity
5
+
6
+ include PowerStencil::SystemEntityDefinitions::HasAssociatedFiles
7
+
8
+ DOC = 'Describes a simple process to be called after source files have been rendered'.freeze
9
+
10
+ entity_type :simple_exec
11
+
12
+ buildable_by ''
13
+
14
+ has_one :process_descriptor, name: :post_process
15
+ not_null :post_process
16
+
17
+ def valid?(raise_error: false)
18
+ unless super(raise_error: false)
19
+ if self.post_process.nil?
20
+ self.post_process = PowerStencil.project.engine.new_entity universe, :process_descriptor, fields: {
21
+ name: "simple_exec_#{name}.process",
22
+ process: './main.sh'
23
+ }
24
+ end
25
+ end
26
+ super(raise_error: raise_error)
27
+ end
28
+
29
+ def save(uri = source_uri, force_files_generation: false)
30
+ valid?
31
+ self.post_process.save
32
+ super(uri, force_files_generation: force_files_generation)
33
+ end
34
+
35
+ def delete(force_files_deletion: false)
36
+ super
37
+ if self.post_process.name == "simple_exec_#{name}.process"
38
+ self.post_process.delete
39
+ end
40
+ self
41
+ end
42
+
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ module PowerStencil
2
+ module Utils
3
+
4
+ module DirectoryProcessor
5
+
6
+ def process_directory(source: nil,
7
+ destination: nil,
8
+ ignore_files_pattern: DirGlobIgnore::IgnoreFileLists::DEFAULT_FILE_NAME)
9
+ raise PowerStencil::Error, 'Source and destination have to be different' if source == destination
10
+ source = check_source_dir source
11
+ destination = check_destination_dir destination
12
+ # Dir.glob_with_ignore_file ignore_file, base_dir, *glob_args, &block
13
+ file_pattern = "#{source}/**/*"
14
+ res = {}
15
+ Dir.glob_with_ignore_file ignore_files_pattern, source, file_pattern, File::FNM_DOTMATCH do |original_file|
16
+ logger.debug "Processing '#{original_file}'"
17
+ res[original_file] = destination_file(original_file, source, destination)
18
+ if block_given?
19
+ yield original_file, destination_file(original_file, source, destination)
20
+ end
21
+ end
22
+ res
23
+ end
24
+
25
+ private
26
+
27
+ def destination_file(original_file, source, destination)
28
+ original_file.gsub /^#{source}/, destination
29
+ end
30
+
31
+ def check_source_dir(source)
32
+ raise PowerStencil::Error, 'Please specify a source directory !' if source.nil?
33
+ source = File.expand_path source
34
+ raise PowerStencil::Error, "Source directory '#{source}' does not exist !" unless File.exist? source
35
+ source
36
+ end
37
+
38
+ def check_destination_dir(destination)
39
+ raise PowerStencil::Error, 'Please specify a destination directory !' if destination.nil?
40
+ destination = File.expand_path destination
41
+ if File.exist? destination
42
+ raise PowerStencil::Error, "Destination '#{destination}' is not a directory !" unless File.directory? destination
43
+ end
44
+ # raise PowerStencil::Error, "Destination '#{destination}' is not a directory !" unless File.directory? destination
45
+ # raise PowerStencil::Error, "Destination directory '#{destination}' already exists !" if File.exist? destination
46
+ destination
47
+
48
+ end
49
+
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,87 @@
1
+ require 'tempfile'
2
+ require 'fileutils'
3
+
4
+ module PowerStencil
5
+ module Utils
6
+
7
+ module FileEdit
8
+
9
+ include Climatic::Proxy
10
+
11
+ EDITOR_ENVIRONMENT_VARIABLE = 'EDITOR'
12
+ LAST_EDITED_FILE = '/tmp/last-failed-edit.yaml'
13
+
14
+ attr_writer :editor, :nb_max_edit_retries
15
+
16
+ def editor
17
+ default = config[:editor] || ENV[EDITOR_ENVIRONMENT_VARIABLE]
18
+ @editor ||= default
19
+ end
20
+
21
+ def nb_max_edit_retries
22
+ @nb_max_edit_retries ||= config[:max_file_edit_retry_times]
23
+ end
24
+
25
+ def securely_edit_entity(entity, &modifications_validation_block)
26
+ initial_uri = entity.source_uri
27
+ modified_entity = nil
28
+ Tempfile.create([entity.type.to_s, entity.name.to_s]) do |tmpfile|
29
+ tmpfile.puts entity.to_yaml
30
+ tmpfile.flush
31
+ securely_edit_file(tmpfile, &modifications_validation_block)
32
+ modified_entity = UniverseCompiler::Entity::Persistence.load tmpfile
33
+ modified_entity.source_uri = initial_uri
34
+ end
35
+ return modified_entity
36
+ end
37
+
38
+ def securely_edit_file(file, &modifications_validation_block)
39
+ unless File.exists? file and File.writable? file
40
+ raise PowerStencil::Error, "Cannot edit file '#{file}' !"
41
+ end
42
+ ext = File.extname file
43
+ base = File.basename file, ext
44
+ tmp_file_path = Tempfile.new([base, ext]).path
45
+ retry_count = 1
46
+ begin
47
+ FileUtils.copy file, tmp_file_path if retry_count == 1
48
+ edit_file tmp_file_path
49
+ if block_given?
50
+ unless modifications_validation_block.call tmp_file_path, file
51
+ raise PowerStencil::Error, "Your modifications to '#{file}' are invalid !"
52
+ end
53
+ end
54
+ FileUtils.copy tmp_file_path, file
55
+ logger.info "File '#{file}' updated successfully."
56
+ file
57
+ rescue StandardError => e
58
+ retry_count += 1
59
+ if retry_count > nb_max_edit_retries
60
+ logger.error "Your modifications are kept in '#{LAST_EDITED_FILE}' as it was invalid !"
61
+ begin
62
+ FileUtils.copy tmp_file_path, LAST_EDITED_FILE
63
+ rescue => ue
64
+ logger.error 'Cannot keep your modifications !'
65
+ logger.debug PowerStencil::Error.report_error(ue)
66
+ end
67
+ else
68
+ retry
69
+ end
70
+ raise e
71
+ ensure
72
+ File.unlink tmp_file_path
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def edit_file(file)
79
+ raise PowerStencil::Error, 'No editor specified' if editor.nil?
80
+ logger.debug "Editing file '#{file}', using editor '#{editor}'"
81
+ system "#{editor} '#{file}'"
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,56 @@
1
+ module PowerStencil
2
+ module Utils
3
+
4
+ module FileHelper
5
+
6
+ def find_recursively_in_path(dir_or_file, from_dir)
7
+ raise PowerStencil::Error, "Invalid directory '#{from_dir}'" unless Dir.exist?(from_dir) and File.readable?(from_dir)
8
+
9
+ candidate = File.join from_dir, dir_or_file
10
+
11
+ if Dir.exists? candidate or File.exist? candidate
12
+ if File.readable? candidate and File.writable? candidate
13
+ candidate
14
+ else
15
+ PowerStencil.logger.warn "Humm, found something: '#{candidate}', but with wrong access rights."
16
+ nil
17
+ end
18
+ else
19
+ next_dir = File.expand_path '..', from_dir
20
+ if next_dir == from_dir
21
+ PowerStencil.logger.info "Didn't find any trace of '#{dir_or_file}'... :("
22
+ nil
23
+ else
24
+ find_recursively_in_path dir_or_file, next_dir
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ def yaml_file_to_hash(file_name)
31
+ raw_content = File.read file_name
32
+ res = YAML.load raw_content
33
+ if res
34
+ Hash[res.map { |k, v| [k, v] }]
35
+ else
36
+ raise PowerStencil::Error, "Invalid file content for '#{file_name}'" unless raw_content.empty?
37
+ []
38
+ end
39
+ end
40
+
41
+ def timestamped_uniq_dir(seed, start_time)
42
+ uniq_dir = timestamp start_time
43
+ uniq_dir += "-#{seed}" unless seed.nil?
44
+ uniq_dir
45
+ end
46
+
47
+ def timestamp(timedate)
48
+ timedate.strftime '%Y%m%d-%H%M-%S%L'
49
+ end
50
+
51
+
52
+
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ module PowerStencil
2
+ module Utils
3
+
4
+ module GemUtils
5
+
6
+ def is_available_gem?(gem_name)
7
+ false
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module PowerStencil
2
+ module Utils
3
+
4
+ module SecureRequire
5
+
6
+ def securely_require(ruby_source_file, fail_on_error: false)
7
+ PowerStencil.logger.debug "Securely requiring Ruby source file '#{ruby_source_file}'..."
8
+ require ruby_source_file
9
+ yield if block_given?
10
+ rescue => e
11
+ PowerStencil.logger.debug PowerStencil::Error.report_error(e)
12
+ msg = "Could not require Ruby source file '#{ruby_source_file}' !"
13
+ if fail_on_error
14
+ msg << ' Aborting !'
15
+ PowerStencil.logger.error msg
16
+ raise PowerStencil::Error, "Invalid Ruby source file: '#{ruby_source_file}' !"
17
+ else
18
+ msg << " (#{e.message})"
19
+ msg << ' Ignoring...'
20
+ PowerStencil.logger.warn msg
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,128 @@
1
+ module PowerStencil
2
+ module Utils
3
+
4
+ class SemanticVersion
5
+
6
+ include Comparable
7
+
8
+ MANDATORY_LEVELS = %i(major minor patch).freeze
9
+ OPTIONAL_LEVEL = :extra
10
+
11
+ class << self
12
+ def valid_version?(version, raise_error: false)
13
+ new version
14
+ true
15
+ rescue => e
16
+ raise e if raise_error
17
+ false
18
+ end
19
+ end
20
+
21
+ def initialize(semantic_version)
22
+ @version = analyze_semantic_version semantic_version
23
+ end
24
+
25
+ def <=>(other)
26
+ other_hash = analyze_semantic_version other
27
+ MANDATORY_LEVELS.each do |level|
28
+ r = @version[level] <=> other_hash[level]
29
+ return r unless r == 0
30
+ end
31
+ if @version[OPTIONAL_LEVEL].nil? and other_hash[OPTIONAL_LEVEL]
32
+ return 1
33
+ end
34
+ unless @version[OPTIONAL_LEVEL].nil?
35
+ return -1 if other_hash[OPTIONAL_LEVEL].nil?
36
+ return @version[OPTIONAL_LEVEL] <=> other_hash[OPTIONAL_LEVEL]
37
+ end
38
+ 0
39
+ end
40
+
41
+ def increment(level = :patch)
42
+ raise PowerStencil::Error, "'#{level}' is not a valid level !" unless MANDATORY_LEVELS.include? level
43
+ h = self.to_hash
44
+ increment_hash(h, level)
45
+ self.class.new h
46
+ end
47
+ alias :succ :increment
48
+
49
+
50
+ def increment!(level = :patch)
51
+ raise PowerStencil::Error, "'#{level}' is not a valid level !" unless MANDATORY_LEVELS.include? level
52
+ increment_hash(@version, level)
53
+ self
54
+ end
55
+ alias :succ! :increment!
56
+
57
+ def to_s
58
+ hash_to_version_string @version
59
+ end
60
+
61
+ def to_hash
62
+ @version.dup
63
+ end
64
+
65
+ private
66
+
67
+ def increment_hash(hash, level)
68
+ hash[level] = hash[level].succ
69
+ hash[:patch] = 0 if level == :minor
70
+ if level == :major
71
+ hash[:minor] = 0
72
+ hash[:patch] = 0
73
+ end
74
+ end
75
+
76
+ def analyze_semantic_version(semantic_version)
77
+ case semantic_version
78
+ when String
79
+ version_string_to_hash semantic_version
80
+ when SemanticVersion
81
+ semantic_version.to_hash
82
+ when Hash
83
+ MANDATORY_LEVELS.each do |level|
84
+ raise_analysis_error semantic_version unless semantic_version.keys.include? level
85
+ raise_analysis_error semantic_version unless semantic_version[level].is_a? Fixnum
86
+ end
87
+ semantic_version.dup
88
+ else
89
+ raise_analysis_error semantic_version
90
+ end
91
+ end
92
+
93
+ def raise_analysis_error(obj)
94
+ raise PowerStencil::Error, "#{obj.class} cannot be used as semantic version !"
95
+ end
96
+
97
+
98
+ def hash_to_version_string(hash)
99
+ version_string = MANDATORY_LEVELS.map {|l| hash[l]}.join '.'
100
+ unless hash[OPTIONAL_LEVEL].nil?
101
+ version_string << "-#{hash[OPTIONAL_LEVEL]}"
102
+ end
103
+ version_string
104
+ end
105
+
106
+ def version_string_to_hash(version_string)
107
+ res = {}
108
+ begin
109
+ version_string.match(/^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<extra>.*))*$/) do |md|
110
+ res = {
111
+ major: md['major'].to_i,
112
+ minor: md['minor'].to_i,
113
+ patch: md['patch'].to_i,
114
+ extra: md['extra'],
115
+ }
116
+ return res
117
+ end
118
+ raise
119
+ rescue
120
+ raise PowerStencil::Error, "Invalid semantic version '#{version_string}' !"
121
+ end
122
+ res
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end