ConfigLMM 0.1.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.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +4 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Examples/Android.mm.yaml +8 -0
  6. data/Examples/Apps/Blog.mm.yaml +7 -0
  7. data/Examples/Apps/Jellyfin.mm.yaml +3 -0
  8. data/Examples/Implemented.mm.yaml +155 -0
  9. data/Examples/Keys.ini +7 -0
  10. data/Examples/Linux.mm.yaml +16 -0
  11. data/Examples/Windows.mm.yaml +11 -0
  12. data/Examples/configlmmAuth.sh +26 -0
  13. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.conf.erb +38 -0
  14. data/Plugins/Apps/ArchiSteamFarm/ArchiSteamFarm.lmm.rb +19 -0
  15. data/Plugins/Apps/IPFS/IPFS.conf.erb +44 -0
  16. data/Plugins/Apps/IPFS/IPFS.lmm.rb +23 -0
  17. data/Plugins/Apps/InfluxDB/InfluxDB.conf.erb +34 -0
  18. data/Plugins/Apps/InfluxDB/InfluxDB.lmm.rb +19 -0
  19. data/Plugins/Apps/Jackett/Jackett.conf.erb +38 -0
  20. data/Plugins/Apps/Jackett/Jackett.lmm.rb +19 -0
  21. data/Plugins/Apps/Jellyfin/Jellyfin.conf.erb +59 -0
  22. data/Plugins/Apps/Jellyfin/Jellyfin.lmm.rb +23 -0
  23. data/Plugins/Apps/Mastodon/Mastodon.conf.erb +81 -0
  24. data/Plugins/Apps/Mastodon/Mastodon.lmm.rb +23 -0
  25. data/Plugins/Apps/Matrix/Matrix.conf.erb +36 -0
  26. data/Plugins/Apps/Matrix/Matrix.lmm.rb +23 -0
  27. data/Plugins/Apps/Netdata/Netdata.conf.erb +37 -0
  28. data/Plugins/Apps/Netdata/Netdata.lmm.rb +23 -0
  29. data/Plugins/Apps/Nextcloud/Nextcloud.conf.erb +165 -0
  30. data/Plugins/Apps/Nextcloud/Nextcloud.lmm.rb +23 -0
  31. data/Plugins/Apps/Nginx/config-lmm/errors.conf +31 -0
  32. data/Plugins/Apps/Nginx/config-lmm/private.conf +6 -0
  33. data/Plugins/Apps/Nginx/config-lmm/proxy.conf +15 -0
  34. data/Plugins/Apps/Nginx/config-lmm/public.conf +3 -0
  35. data/Plugins/Apps/Nginx/config-lmm/ssl.conf +18 -0
  36. data/Plugins/Apps/Nginx/main.conf +30 -0
  37. data/Plugins/Apps/Nginx/nginx.conf +90 -0
  38. data/Plugins/Apps/Nginx/nginx.lmm.rb +62 -0
  39. data/Plugins/Apps/Nginx/proxy.conf.erb +31 -0
  40. data/Plugins/Apps/Odoo/Odoo.conf.erb +44 -0
  41. data/Plugins/Apps/Odoo/Odoo.lmm.rb +23 -0
  42. data/Plugins/Apps/Pterodactyl/Pterodactyl.conf.erb +50 -0
  43. data/Plugins/Apps/Pterodactyl/Pterodactyl.lmm.rb +30 -0
  44. data/Plugins/Apps/Pterodactyl/Wings.conf.erb +38 -0
  45. data/Plugins/Apps/Sunshine/Sunshine.conf.erb +31 -0
  46. data/Plugins/Apps/Sunshine/Sunshine.lmm.rb +21 -0
  47. data/Plugins/Apps/Vaultwarden/Vaultwarden.conf.erb +48 -0
  48. data/Plugins/Apps/Vaultwarden/Vaultwarden.lmm.rb +25 -0
  49. data/Plugins/Apps/bitmagnet/bitmagnet.conf.erb +35 -0
  50. data/Plugins/Apps/bitmagnet/bitmagnet.lmm.rb +19 -0
  51. data/Plugins/Apps/gollum/config.ru +11 -0
  52. data/Plugins/Apps/gollum/gollum.conf.erb +41 -0
  53. data/Plugins/Apps/gollum/gollum.lmm.rb +52 -0
  54. data/Plugins/OS/Linux.lmm.rb +64 -0
  55. data/Plugins/OS/Routers/Aruba/ArubaInstant.lmm.rb +144 -0
  56. data/Plugins/Platforms/GitHub.lmm.rb +57 -0
  57. data/Plugins/Platforms/GoDaddy/GoDaddy.lmm.rb +83 -0
  58. data/Plugins/Platforms/GoDaddy/zone.txt.erb +13 -0
  59. data/Plugins/Platforms/porkbun.lmm.rb +129 -0
  60. data/Plugins/Platforms/porkbun_spec.rb +110 -0
  61. data/Plugins/Services/DNS/AmberBit.lmm.rb +14 -0
  62. data/Plugins/Services/DNS/ArubaItDNS.lmm.rb +14 -0
  63. data/Plugins/Services/DNS/NICLV.lmm.rb +18 -0
  64. data/Plugins/Services/DNS/PowerDNS.lmm.rb +261 -0
  65. data/Plugins/Services/DNS/tonic.lmm.rb +126 -0
  66. data/README.md +337 -0
  67. data/Rakefile +15 -0
  68. data/UNLICENSE +24 -0
  69. data/bin/configlmm +7 -0
  70. data/bin/console +11 -0
  71. data/bin/setup +8 -0
  72. data/lib/ConfigLMM/Framework/plugins/dns.rb +63 -0
  73. data/lib/ConfigLMM/Framework/plugins/errors.rb +23 -0
  74. data/lib/ConfigLMM/Framework/plugins/nginxApp.rb +55 -0
  75. data/lib/ConfigLMM/Framework/plugins/plugin.rb +167 -0
  76. data/lib/ConfigLMM/Framework/plugins/ssh.rb +37 -0
  77. data/lib/ConfigLMM/Framework/plugins/store.rb +57 -0
  78. data/lib/ConfigLMM/Framework/plugins.rb +5 -0
  79. data/lib/ConfigLMM/Framework/registrator.rb +32 -0
  80. data/lib/ConfigLMM/Framework.rb +9 -0
  81. data/lib/ConfigLMM/LMM/plugins.rb +5 -0
  82. data/lib/ConfigLMM/LMM.rb +8 -0
  83. data/lib/ConfigLMM/cli.rb +161 -0
  84. data/lib/ConfigLMM/command.rb +53 -0
  85. data/lib/ConfigLMM/commands/build.rb +41 -0
  86. data/lib/ConfigLMM/commands/cleanup.rb +30 -0
  87. data/lib/ConfigLMM/commands/configsCommand.rb +167 -0
  88. data/lib/ConfigLMM/commands/deploy.rb +39 -0
  89. data/lib/ConfigLMM/commands/diff.rb +45 -0
  90. data/lib/ConfigLMM/commands/list.rb +15 -0
  91. data/lib/ConfigLMM/commands/refresh.rb +46 -0
  92. data/lib/ConfigLMM/commands/types.rb +35 -0
  93. data/lib/ConfigLMM/commands/validate.rb +49 -0
  94. data/lib/ConfigLMM/context.rb +52 -0
  95. data/lib/ConfigLMM/io/configList.rb +98 -0
  96. data/lib/ConfigLMM/io/path.rb +48 -0
  97. data/lib/ConfigLMM/io/source.rb +47 -0
  98. data/lib/ConfigLMM/io.rb +2 -0
  99. data/lib/ConfigLMM/state.rb +78 -0
  100. data/lib/ConfigLMM/utils/filters.rb +126 -0
  101. data/lib/ConfigLMM/version.rb +5 -0
  102. data/lib/ConfigLMM.rb +6 -0
  103. data/sig/ConfigLMM.rbs +4 -0
  104. metadata +485 -0
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+ require_relative '../utils/filters'
5
+ require_relative '../io/configList'
6
+ require_relative '../context'
7
+ require_relative '../state'
8
+ require_relative '../Framework'
9
+ require_relative '../LMM'
10
+ require 'xdg'
11
+ require 'tmpdir'
12
+
13
+ module ConfigLMM
14
+ module Commands
15
+ class ConfigsCommand < ConfigLMM::Command
16
+
17
+ def initialize(configPaths, options)
18
+ @ConfigPaths = configPaths
19
+ @Options = options
20
+ @Plugins = {}
21
+
22
+ logger do |config|
23
+ config.level = @Options[:level]
24
+ end
25
+
26
+ @Context = Context.new(logger, prompt, ::XDG.new, @Options[:context])
27
+ @State = State.new(logger, prompt)
28
+ @Diff = {}
29
+
30
+ # Load all Plugin files
31
+ Framework::Registrator.registerAll(logger)
32
+
33
+ # Create Plugin instances
34
+ Framework::Store.boot(logger, prompt, @Plugins)
35
+ end
36
+
37
+ def execute
38
+ raise ConfigLMM::CLI::MissingArgument.new("ERROR: No configs specified!\n\n") if @ConfigPaths.empty?
39
+
40
+ options = @Options.dup
41
+ options.delete(:locations)
42
+ options.delete(:things)
43
+ #options[:locationFilter] = Utils::Filters.parseLocationsOption(@Options[:locations], logger)
44
+ #options[:thingFilter] = Utils::Filters.parseThingsOption(@Options[:things], logger)
45
+
46
+ configList = IO::ConfigList.create(@ConfigPaths, logger)
47
+ configList.expand!(options[:locationFilter])
48
+
49
+ @State.load!(configList, options)
50
+
51
+ self.processConfig(configList.toConfig(@Context), options)
52
+ end
53
+
54
+ def plugins
55
+ @Plugins
56
+ end
57
+
58
+ def state
59
+ @State
60
+ end
61
+
62
+ def context
63
+ @Context
64
+ end
65
+
66
+ def findBestProvider(plugins)
67
+ raise 'No providers!' if plugins.empty?
68
+ # TODO FIXME
69
+ # In case of multiple providers that match
70
+ # We should chose best one and save it in state
71
+ plugins.first
72
+ end
73
+
74
+ protected
75
+
76
+ def invokeValidateAction(id, plugin, singleTarget, options)
77
+ actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Validate')
78
+ plugin.send(actionMethod, id, singleTarget, state, context, options)
79
+ end
80
+
81
+ def invokeRefreshAction(id, plugin, singleTarget, options)
82
+ state.create!
83
+ activeState = state.item(id)
84
+ if activeState[:Type].nil?
85
+ activeState[:Type] = singleTarget['Type'].to_s
86
+ elsif activeState[:Type] != singleTarget['Type'].to_s
87
+ raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
88
+ end
89
+ actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Refresh')
90
+ if plugin.methods.include?(:authenticate)
91
+ result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
92
+ raise Framework::PluginAuthError.new('Failed to authenticate!') unless result
93
+ end
94
+ plugin.send(actionMethod, id, singleTarget, activeState, context, options)
95
+ state.save
96
+ end
97
+
98
+ def invokeDiffAction(id, plugin, singleTarget, options)
99
+ state.create!
100
+ activeState = state.item(id)
101
+ if activeState[:Type].nil?
102
+ activeState[:Type] = singleTarget['Type'].to_s
103
+ elsif activeState[:Type] != singleTarget['Type'].to_s
104
+ raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
105
+ end
106
+ actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Diff')
107
+ plugin.send(actionMethod, id, singleTarget, activeState, context, options)
108
+ end
109
+
110
+ def invokeBuildAction(id, plugin, singleTarget, options)
111
+ actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Build')
112
+ plugin.send(actionMethod, id, singleTarget, state, context, options)
113
+ end
114
+
115
+ def invokeDeployAction(id, plugin, singleTarget, options)
116
+ prompt.warn("Deploying #{singleTarget['ID']}: #{singleTarget['Type'].to_s}")
117
+ state.create!
118
+ activeState = state.item(id)
119
+ if activeState[:Type].nil?
120
+ activeState[:Type] = singleTarget['Type'].to_s
121
+ elsif activeState[:Type] != singleTarget['Type'].to_s
122
+ raise Framework::PluginError.new("Unexpected Type #{activeState[:Type].inspect}! Wanted #{singleTarget['Type']}")
123
+ end
124
+ actionMethod = plugin.class.actionMethod(singleTarget['Type'], 'Deploy')
125
+ if plugin.methods.include?(:authenticate)
126
+ result = plugin.authenticate(actionMethod, singleTarget, state, context, options)
127
+ raise Framework::PluginAuthError.new('Failed to authenticate!') unless result
128
+ end
129
+
130
+ Dir.mktmpdir do |outputDir|
131
+
132
+ if plugin.class.persistBuildDir?
133
+ if options['output'] == '/tmp or ./build'
134
+ options = options.dup
135
+ options['output'] = './build'
136
+ if !options['dry']
137
+ FileUtils.mkdir_p(options['output'])
138
+ end
139
+ end
140
+ else
141
+ options = options.dup
142
+ options['output'] = outputDir
143
+ end
144
+
145
+ if !options['dry']
146
+ # Prevent others accessing it
147
+ FileUtils.chmod(0750, options['output'])
148
+ end
149
+
150
+ if plugin.hasAction?(singleTarget['Type'], :build)
151
+ invokeBuildAction(id, plugin, singleTarget, options)
152
+ end
153
+
154
+ plugin.send(actionMethod, id, singleTarget, activeState, context, options)
155
+ end
156
+ state.save
157
+ end
158
+
159
+
160
+ def checkDiff(id, targetName, stateName, target, activeState)
161
+ if target[targetName] != activeState[stateName]
162
+ @Diff[id] = target
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configsCommand'
4
+
5
+ module ConfigLMM
6
+ module Commands
7
+ class Deploy < ConfigsCommand
8
+ def processConfig(config, options)
9
+ config.each do |id, target|
10
+
11
+ target['Resources'].to_h.each do |id, config|
12
+ IO::ConfigList.processConfig(id, config, target[:Parent])
13
+ processDeploy(IO::ConfigList.normalizeId(id), config, options)
14
+ end
15
+
16
+ processDeploy(id, target, options)
17
+ end
18
+ prompt.ok('Deploy successful!')
19
+ end
20
+
21
+ def processDeploy(id, target, options)
22
+ providers = []
23
+ self.plugins.each do |pluginId, plugin|
24
+ if plugin.hasAction?(target['Type'], :deploy)
25
+ providers << plugin
26
+ end
27
+ end
28
+ message = "Couldn't find action Build for #{target['Type']}"
29
+ if providers.empty?
30
+ logger.debug(message)
31
+ return
32
+ end
33
+
34
+ bestProvider = self.findBestProvider(providers)
35
+ invokeDeployAction(id, bestProvider, target, options)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configsCommand'
4
+
5
+ module ConfigLMM
6
+ module Commands
7
+ class Diff < ConfigsCommand
8
+ def processConfig(config, options)
9
+ configDiffs = {}
10
+ config.each do |id, data|
11
+ found = false
12
+ plugins.each do |pluginId, plugin|
13
+ if plugin.hasAction?(data['Type'], :diff)
14
+ invokeDiffAction(id, plugin, data, options)
15
+ configDiffs[id] = plugin.diff unless plugin.diff.empty?
16
+ found = true
17
+ end
18
+ end
19
+ logger.debug("Couldn't find action Diff for type #{data['Type']}") unless found
20
+ end
21
+ showDiff(configDiffs)
22
+ end
23
+
24
+ def showDiff(configDiffs)
25
+ configDiffs.each do |id, diffs|
26
+ prompt.say(' ' + id + ':')
27
+ diffs.each do |name, diff|
28
+ if diff.first.is_a?(Hash)
29
+ prompt.say(' ' + name + ':')
30
+ diff.first.each do |name, value|
31
+ prompt.say('- ' + name + ': ' + value, :color => :red)
32
+ end
33
+ diff.last.each do |name, value|
34
+ prompt.say('+ ' + name + ': ' + value, :color => :green)
35
+ end
36
+ else
37
+ prompt.say('- ' + name + ': ' + diff.first, :color => :red)
38
+ prompt.say('+ ' + name + ': ' + diff.last, :color => :green)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configsCommand'
4
+
5
+ module ConfigLMM
6
+ module Commands
7
+ class List < ConfigsCommand
8
+ def processConfig(config, options)
9
+ config.each do |id, data|
10
+ prompt.say("#{data['Name']}: #{data['Type']}")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configsCommand'
4
+
5
+ module ConfigLMM
6
+ module Commands
7
+ class Refresh < ConfigsCommand
8
+ def processConfig(config, options)
9
+ errors = 0
10
+ config.each do |id, target|
11
+
12
+ target['Resources'].to_h.each do |id, target|
13
+ IO::ConfigList.processConfig(id, target, target[:Parent])
14
+ processRefresh(IO::ConfigList.normalizeId(id), target, options)
15
+ end
16
+
17
+ errors += processRefresh(id, target, options)
18
+ end
19
+ if errors.zero?
20
+ prompt.ok('Refresh successful!')
21
+ else
22
+ prompt.error('Encountered issue while refreshing state!')
23
+ end
24
+ end
25
+
26
+ def processRefresh(id, target, options)
27
+ errors = 0
28
+ found = false
29
+ self.plugins.each do |pluginId, plugin|
30
+ if plugin.hasAction?(target['Type'], :refresh)
31
+ begin
32
+ invokeRefreshAction(id, plugin, target, options)
33
+ rescue Framework::PluginError => e
34
+ logger.error(e)
35
+ errors += 1
36
+ end
37
+ found = true
38
+ end
39
+ end
40
+ # We allow plugins without refresh action
41
+ logger.debug("Couldn't find action Refresh for type #{target['Type']}") unless found
42
+ errors
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+ require_relative '../Framework'
5
+ require 'set'
6
+ require 'yaml'
7
+
8
+ module ConfigLMM
9
+ module Commands
10
+ class Types < ConfigLMM::Command
11
+ def initialize(options)
12
+ logger do |config|
13
+ config.level = options[:level]
14
+ end
15
+
16
+ # Load all Plugin files
17
+ Framework::Registrator.registerAll(logger)
18
+ end
19
+
20
+ def execute
21
+ types = {}
22
+ Framework::Store.plugins.each do |plugin|
23
+ plugin.instance_methods.each do |method|
24
+ if match = method.match('^action(\w+)(Validate|Build|Refresh|Diff|Deploy)$')
25
+ type = match[1]
26
+ types[type] ||= []
27
+ types[type] << match[2]
28
+ end
29
+ end
30
+ end
31
+ prompt.say(YAML.dump({ 'Types' => types.sort.to_h }))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configsCommand'
4
+
5
+ module ConfigLMM
6
+ module Commands
7
+ class Validate < ConfigsCommand
8
+ def processConfig(config, options)
9
+
10
+ errors = 0
11
+ config.each do |id, target|
12
+ target['Resources'].to_h.each do |id, target|
13
+ IO::ConfigList.processConfig(id, target, target[:Parent])
14
+ processValidate(IO::ConfigList.normalizeId(id), target, options)
15
+ end
16
+
17
+ errors += 1 unless processValidate(id, target, options)
18
+ end
19
+ if errors.zero?
20
+ prompt.ok('Validation successful, no issues were found!')
21
+ else
22
+ prompt.error('Validation failed, we found some issues!')
23
+ end
24
+ end
25
+
26
+ def processValidate(id, target, options)
27
+ providers = []
28
+ self.plugins.each do |pluginId, plugin|
29
+ if plugin.hasAction?(target['Type'], :validate)
30
+ providers << plugin
31
+ end
32
+ end
33
+ if providers.empty?
34
+ # We allow for validation function to not exist
35
+ true
36
+ else
37
+ errors = []
38
+ providers.each do |provider|
39
+ errors += invokeValidateAction(id, provider, target, options)
40
+ end
41
+ errors.each do |error|
42
+ prompt.error(error)
43
+ end
44
+ errors.empty?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,52 @@
1
+
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ require 'yaml'
6
+
7
+ module ConfigLMM
8
+ class Context
9
+ CONTEXT_FILE = 'configlmm/context.yaml'
10
+
11
+ def initialize(logger, prompt, xdg, contextFile)
12
+ @Logger = logger
13
+ @Prompt = prompt
14
+ load!(xdg.config_home, contextFile)
15
+ end
16
+
17
+ def likes?(name)
18
+ @Context['Likes'].include?(name)
19
+ end
20
+
21
+ def dislikes?(name)
22
+ @Context['Dislikes'].include?(name)
23
+ end
24
+
25
+ def add(context)
26
+ return unless context
27
+ context['Likes'] ||= []
28
+ context['Dislikes'] ||= []
29
+ @Context['Likes'] += context['Likes']
30
+ @Context['Dislikes'] += context['Dislikes']
31
+ end
32
+
33
+ private
34
+
35
+ def load!(configHome, contextFile)
36
+ @Context = {}
37
+ if (contextFile && !File.exist?(contextFile))
38
+ @Logger.error("Provided Context file doesn't exist: #{contextFile}")
39
+ raise 'Missing Context!'
40
+ end
41
+ if !contextFile
42
+ contextFile = configHome / CONTEXT_FILE
43
+ end
44
+ if (File.exist?(contextFile))
45
+ @Context = YAML.safe_load_file(contextFile, permitted_classes: [Symbol])
46
+ end
47
+ @Context['Likes'] ||= []
48
+ @Context['Dislikes'] ||= []
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'path'
4
+ require 'find'
5
+ require 'yaml'
6
+
7
+ module ConfigLMM
8
+ module IO
9
+ class ConfigList
10
+ ConfigError = Class.new(RuntimeError)
11
+
12
+ def self.create(targets, logger)
13
+ targets = targets.uniq.select do |target|
14
+ exist = File.exist?(target)
15
+ logger.warn("'#{path}' doesn't exist, ignoring!") unless exist
16
+ exist
17
+ end
18
+ self.new(targets)
19
+ end
20
+
21
+ def initialize(targets)
22
+ @Sources = targets.map do |target|
23
+ Path.new(target)
24
+ end
25
+ end
26
+
27
+ def expand!(locationFilter)
28
+ sources = []
29
+ @Sources.each do |source|
30
+ basePath = source.to_s
31
+ if File.file?(basePath)
32
+ parent = Path.new(File.dirname(File.expand_path(basePath)))
33
+ sources << Path.new(basePath, parent)
34
+ else
35
+ parent = source
36
+ ::Find.find(basePath) do |path|
37
+ next unless Path.isConfig?(path)
38
+ parent = parent.lookupParent(path)
39
+ if File.directory?(path)
40
+ parent = Path.new(path, parent)
41
+ next
42
+ end
43
+ path = Path.new(path, parent)
44
+ sources << path if Utils::Filters.includePath?(path, locationFilter)
45
+ end
46
+ end
47
+ end
48
+ @Sources = sources
49
+ end
50
+
51
+ def self.normalizeId(id)
52
+ # Remove all non-letters but allow Unicode
53
+ id.gsub(/[[:space:]]/, '').upcase
54
+ end
55
+
56
+ def self.processConfig(id, data, parent)
57
+ data['ID'] = id
58
+ if data['Type'].nil?
59
+ raise ConfigError.new("Missing 'Type' field: #{id}!")
60
+ end
61
+ data['Name'] = id unless data.has_key?('Name')
62
+ data['Type'] = data['Type'].to_sym
63
+ data[:Parent] = parent
64
+ data
65
+ end
66
+
67
+ def toConfig(context)
68
+ config = {}
69
+ @Sources.each do |source|
70
+ YAML.safe_load_file(source.to_s, permitted_classes: [Symbol]).each do |id, data|
71
+ normalizedId = self.class.normalizeId(id)
72
+ if id == '_CONTEXT_'
73
+ context.add(data)
74
+ next
75
+ end
76
+
77
+ self.class.processConfig(id, data, source.parent)
78
+
79
+ # TODO FIXME we should deep merge them instead
80
+ raise ConfigError.new("Duplicate ID: #{id} (#{normalizedId}) - #{source}") if config.has_key?(normalizedId)
81
+ config[normalizedId] = data
82
+ end
83
+ #rescue YAML::SyntaxError => error
84
+ # raise ConfigError.new(error)
85
+ end
86
+ config
87
+ end
88
+
89
+ def count
90
+ @Sources.length
91
+ end
92
+
93
+ def to_a
94
+ @Sources
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require_relative 'source'
5
+
6
+ module ConfigLMM
7
+ module IO
8
+ class Path < Source
9
+ def initialize(path, parent = nil)
10
+ @Path = path.is_a?(Pathname) ? path : Pathname.new(path)
11
+ super(parent)
12
+ end
13
+
14
+ def name
15
+ @Path.basename.to_s
16
+ end
17
+
18
+ def basename
19
+ @Path.basename(@Path.extname).to_s
20
+ end
21
+
22
+ def dirname
23
+ parent.basename unless parent.nil?
24
+ end
25
+
26
+ def self.isConfig?(path)
27
+ return false unless File.extname(path) == '.yaml'
28
+ File.basename(path, '.yaml').end_with?('.mm')
29
+ end
30
+
31
+ def to_s
32
+ @Path.to_s
33
+ end
34
+
35
+ def ==(other)
36
+ self.to_s == other.to_s
37
+ end
38
+
39
+ def lookupParent(path)
40
+ if path.start_with?(self.to_s)
41
+ self
42
+ else
43
+ parent&.lookupParent(path) or raise "Didn't find parent for #{path}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConfigLMM
4
+ module IO
5
+ class Source
6
+
7
+ def initialize(parent = nil)
8
+ unless parent.nil? || parent.is_a?(Source)
9
+ raise "Invalid parent! Must be instance of Source! Got #{parent.inspect}"
10
+ end
11
+ @ID = false
12
+ @Parent = parent
13
+ @Childs = {}
14
+ @Parent.addChild(self) if @Parent.is_a?(Source)
15
+ end
16
+
17
+ def id
18
+ return @ID unless @ID == false
19
+ if @Parent.is_a?(Source)
20
+ @ID = self.name
21
+ @ID = @Parent.id + '/' + @ID if @Parent.id
22
+ else
23
+ @ID = nil
24
+ end
25
+ @ID
26
+ end
27
+
28
+ def parent
29
+ @Parent
30
+ end
31
+
32
+ def [](name)
33
+ raise "Didn't find #{name} in #{self.inspect}" unless @Childs.key?(name)
34
+ @Childs[name]
35
+ end
36
+
37
+ protected
38
+
39
+ def addChild(child)
40
+ raise "Invalid child! Must be instance of Source! Got #{child.inspect}" unless child.is_a?(Source)
41
+ return if @Childs.key?(child.name)
42
+ @Childs[child.name] = child
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,2 @@
1
+
2
+ require_relative 'io/configList'