makit 0.0.99 → 0.0.112

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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -0
  3. data/exe/makit +5 -0
  4. data/lib/makit/apache.rb +28 -32
  5. data/lib/makit/cli/build_commands.rb +500 -0
  6. data/lib/makit/cli/generators/base_generator.rb +74 -0
  7. data/lib/makit/cli/generators/dotnet_generator.rb +50 -0
  8. data/lib/makit/cli/generators/generator_factory.rb +49 -0
  9. data/lib/makit/cli/generators/node_generator.rb +50 -0
  10. data/lib/makit/cli/generators/ruby_generator.rb +77 -0
  11. data/lib/makit/cli/generators/rust_generator.rb +50 -0
  12. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -0
  13. data/lib/makit/cli/generators/templates/node_templates.rb +161 -0
  14. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -0
  15. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +40 -0
  16. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -0
  17. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -0
  18. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -0
  19. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -0
  20. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -0
  21. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -0
  22. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -0
  23. data/lib/makit/cli/main.rb +62 -33
  24. data/lib/makit/cli/project_commands.rb +868 -0
  25. data/lib/makit/cli/repository_commands.rb +661 -0
  26. data/lib/makit/cli/utility_commands.rb +521 -0
  27. data/lib/makit/commands/factory.rb +359 -0
  28. data/lib/makit/commands/middleware/base.rb +73 -0
  29. data/lib/makit/commands/middleware/cache.rb +248 -0
  30. data/lib/makit/commands/middleware/command_logger.rb +320 -0
  31. data/lib/makit/commands/middleware/unified_logger.rb +243 -0
  32. data/lib/makit/commands/middleware/validator.rb +269 -0
  33. data/lib/makit/commands/request.rb +254 -0
  34. data/lib/makit/commands/result.rb +323 -0
  35. data/lib/makit/commands/runner.rb +337 -0
  36. data/lib/makit/commands/strategies/base.rb +160 -0
  37. data/lib/makit/commands/strategies/synchronous.rb +134 -0
  38. data/lib/makit/commands.rb +51 -21
  39. data/lib/makit/configuration/gitlab_helper.rb +60 -0
  40. data/lib/makit/configuration/project.rb +127 -0
  41. data/lib/makit/configuration/rakefile_helper.rb +43 -0
  42. data/lib/makit/configuration/step.rb +34 -0
  43. data/lib/makit/configuration.rb +14 -0
  44. data/lib/makit/content/default_gitignore.rb +7 -5
  45. data/lib/makit/content/default_rakefile.rb +13 -11
  46. data/lib/makit/content/gem_rakefile.rb +16 -14
  47. data/lib/makit/context.rb +1 -0
  48. data/lib/makit/data.rb +49 -50
  49. data/lib/makit/directories.rb +141 -145
  50. data/lib/makit/directory.rb +262 -276
  51. data/lib/makit/docs/files.rb +89 -94
  52. data/lib/makit/docs/rake.rb +102 -106
  53. data/lib/makit/dotnet/cli.rb +65 -0
  54. data/lib/makit/dotnet/project.rb +153 -0
  55. data/lib/makit/dotnet/solution.rb +38 -0
  56. data/lib/makit/dotnet/solution_classlib.rb +239 -0
  57. data/lib/makit/dotnet/solution_console.rb +264 -0
  58. data/lib/makit/dotnet/solution_maui.rb +354 -0
  59. data/lib/makit/dotnet/solution_wasm.rb +275 -0
  60. data/lib/makit/dotnet/solution_wpf.rb +304 -0
  61. data/lib/makit/dotnet.rb +102 -219
  62. data/lib/makit/email.rb +90 -61
  63. data/lib/makit/environment.rb +142 -139
  64. data/lib/makit/examples/runner.rb +370 -0
  65. data/lib/makit/exceptions.rb +45 -0
  66. data/lib/makit/fileinfo.rb +24 -26
  67. data/lib/makit/files.rb +43 -47
  68. data/lib/makit/gems.rb +29 -28
  69. data/lib/makit/git/cli.rb +54 -0
  70. data/lib/makit/git/repository.rb +90 -0
  71. data/lib/makit/git.rb +98 -145
  72. data/lib/makit/gitlab_runner.rb +59 -60
  73. data/lib/makit/humanize.rb +137 -129
  74. data/lib/makit/indexer.rb +47 -56
  75. data/lib/makit/logging/configuration.rb +305 -0
  76. data/lib/makit/logging/format_registry.rb +84 -0
  77. data/lib/makit/logging/formatters/base.rb +39 -0
  78. data/lib/makit/logging/formatters/console_formatter.rb +140 -0
  79. data/lib/makit/logging/formatters/json_formatter.rb +65 -0
  80. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -0
  81. data/lib/makit/logging/formatters/text_formatter.rb +64 -0
  82. data/lib/makit/logging/log_request.rb +115 -0
  83. data/lib/makit/logging/logger.rb +163 -0
  84. data/lib/makit/logging/sinks/base.rb +91 -0
  85. data/lib/makit/logging/sinks/console.rb +72 -0
  86. data/lib/makit/logging/sinks/file_sink.rb +92 -0
  87. data/lib/makit/logging/sinks/structured.rb +129 -0
  88. data/lib/makit/logging/sinks/unified_file_sink.rb +303 -0
  89. data/lib/makit/logging.rb +530 -106
  90. data/lib/makit/markdown.rb +75 -75
  91. data/lib/makit/mp/basic_object_mp.rb +17 -16
  92. data/lib/makit/mp/command_mp.rb +13 -13
  93. data/lib/makit/mp/command_request.mp.rb +17 -16
  94. data/lib/makit/mp/project_mp.rb +199 -210
  95. data/lib/makit/mp/string_mp.rb +193 -176
  96. data/lib/makit/nuget.rb +74 -72
  97. data/lib/makit/port.rb +32 -34
  98. data/lib/makit/process.rb +163 -65
  99. data/lib/makit/protoc.rb +107 -104
  100. data/lib/makit/rake/cli.rb +196 -0
  101. data/lib/makit/rake.rb +25 -25
  102. data/lib/makit/ruby/cli.rb +185 -0
  103. data/lib/makit/ruby.rb +25 -0
  104. data/lib/makit/secrets.rb +51 -51
  105. data/lib/makit/serializer.rb +130 -115
  106. data/lib/makit/services/builder.rb +186 -0
  107. data/lib/makit/services/error_handler.rb +226 -0
  108. data/lib/makit/services/repository_manager.rb +229 -0
  109. data/lib/makit/services/validator.rb +112 -0
  110. data/lib/makit/setup/classlib.rb +53 -0
  111. data/lib/makit/setup/gem.rb +263 -0
  112. data/lib/makit/setup/runner.rb +45 -0
  113. data/lib/makit/setup.rb +5 -0
  114. data/lib/makit/show.rb +110 -110
  115. data/lib/makit/storage.rb +126 -131
  116. data/lib/makit/symbols.rb +170 -149
  117. data/lib/makit/task_info.rb +128 -86
  118. data/lib/makit/tasks/at_exit.rb +13 -0
  119. data/lib/makit/tasks/build.rb +19 -0
  120. data/lib/makit/tasks/clean.rb +11 -0
  121. data/lib/makit/tasks/hook_manager.rb +393 -0
  122. data/lib/makit/tasks/init.rb +47 -0
  123. data/lib/makit/tasks/integrate.rb +17 -0
  124. data/lib/makit/tasks/pull_incoming.rb +11 -0
  125. data/lib/makit/tasks/setup.rb +6 -0
  126. data/lib/makit/tasks/sync.rb +12 -0
  127. data/lib/makit/tasks/tag.rb +15 -0
  128. data/lib/makit/tasks/task_monkey_patch.rb +79 -0
  129. data/lib/makit/tasks.rb +15 -150
  130. data/lib/makit/test_cache.rb +239 -0
  131. data/lib/makit/tree.rb +37 -37
  132. data/lib/makit/v1/makit.v1_pb.rb +3 -4
  133. data/lib/makit/v1/makit.v1_services_pb.rb +27 -25
  134. data/lib/makit/version.rb +5 -61
  135. data/lib/makit/version_util.rb +21 -0
  136. data/lib/makit/wix.rb +95 -95
  137. data/lib/makit/yaml.rb +29 -17
  138. data/lib/makit/zip.rb +17 -17
  139. data/lib/makit copy.rb +44 -0
  140. data/lib/makit.rb +40 -267
  141. metadata +117 -110
  142. data/lib/makit/cli/clean.rb +0 -14
  143. data/lib/makit/cli/clone.rb +0 -59
  144. data/lib/makit/cli/init.rb +0 -38
  145. data/lib/makit/cli/make.rb +0 -54
  146. data/lib/makit/cli/new.rb +0 -37
  147. data/lib/makit/cli/nuget_cache.rb +0 -38
  148. data/lib/makit/cli/pull.rb +0 -31
  149. data/lib/makit/cli/setup.rb +0 -71
  150. data/lib/makit/cli/work.rb +0 -21
  151. data/lib/makit/command_runner.rb +0 -404
  152. data/lib/makit/content/default_gitignore.txt +0 -222
@@ -1,129 +1,137 @@
1
- # frozen_string_literal: true
2
-
3
- # This module provides classes for the Makit gem.
4
- module Makit
5
- class Humanize
6
- def self.get_humanized_size(bytes, precision = 2)
7
- units = ["B", "KB", "MB", "GB", "TB", "PB"]
8
- return "0 B" if bytes == 0
9
-
10
- exp = (Math.log(bytes) / Math.log(1024)).to_i
11
- exp = units.size - 1 if exp >= units.size
12
-
13
- size = bytes.to_f / (1024 ** exp)
14
- format("%.#{precision}f %s", size, units[exp])
15
- end
16
-
17
- def self.get_humanized_timestamp(timestamp)
18
- return timestamp.strftime("%Y-%m-%d %I:%M:%S %p") if timestamp.respond_to?(:strftime)
19
- timestamp.strftime("%Y-%m-%d %H:%M:%S")
20
- end
21
-
22
- def self.get_make_result_summary(make_result)
23
- summary = "Make Result\n"
24
- summary += " Repository: #{make_result.repository}\n"
25
- summary += " Commit: #{make_result.commit}\n"
26
- summary += " Branch: #{make_result.branch}\n"
27
- summary += " Tag: #{make_result.tag}\n"
28
- summary += " Device: #{make_result.device}\n"
29
- summary += " Runtime Identifier: #{make_result.runtime_identifier}\n"
30
- summary += " Initial Size: #{get_humanized_size(make_result.initial_size)}\n"
31
- summary += " Final Size: #{get_humanized_size(make_result.final_size)}\n"
32
- summary += " Delta Size: #{get_humanized_size(make_result.final_size - make_result.initial_size)}\n"
33
- summary += " Commands: (#{make_result.commands.length})\n"
34
- make_result.commands.each do |command|
35
- details = get_command_details(command)
36
- summary += "\n"
37
- summary += indent_string(details, 4)
38
- summary += "\n"
39
- end
40
-
41
- summary
42
- end
43
-
44
- def self.get_commands(commands)
45
- message = ""
46
- commands.each do |command|
47
- message += Makit::Humanize::get_command_details(command)
48
- end
49
- message
50
- end
51
-
52
- def self.get_command_summary(command)
53
- symbol = Makit::Symbols.warning
54
- symbol = Makit::Symbols.checkmark if !command.exit_code.nil? && command.exit_code.zero?
55
- symbol = Makit::Symbols.error if command.exit_code != 0
56
- "#{symbol} #{command.name} #{command.arguments.join(" ")}"
57
- end
58
-
59
- def self.get_command_details(command)
60
- summary = "#{get_command_summary(command)}\n"
61
- summary += " Name: #{command.name}\n"
62
- summary += " Arguments: #{command.arguments.join(" ")}\n"
63
- summary += " Directory: #{command.directory}\n"
64
- summary += " Exit Code: #{command.exit_code}\n"
65
- if command.output.length > 0
66
- summary += " Output:\n"
67
- summary += indent_string(command.output, 4)
68
- summary += "\n"
69
- end
70
- if command.error.length > 0
71
- summary += " Error:\n"
72
- summary += indent_string(command.error, 4)
73
- summary += "\n"
74
- end
75
- summary
76
- end
77
-
78
- def self.indent_string(string, spaces)
79
- string.split("\n").map { |line| " " * spaces + line }.join("\n")
80
- end
81
-
82
- def self.get_protobuf_timestamp(timestamp)
83
- Time.at(timestamp.seconds, timestamp.nanos / 1000.0).strftime("%Y-%m-%d %H:%M:%S")
84
- end
85
-
86
- def self.get_protobuf_duration(duration)
87
- total_seconds = duration.seconds + (duration.nanos / 1_000_000_000.0)
88
- hours = (total_seconds / 3600).to_i
89
- minutes = ((total_seconds % 3600) / 60).to_i
90
- seconds = (total_seconds % 60).round(2)
91
- "#{hours}h #{minutes}m #{seconds}s"
92
- end
93
-
94
- def self.get_humanized_duration(seconds_value)
95
- minutes = (seconds_value / 60).to_i
96
- seconds = (seconds_value % 60).to_i
97
- hours = (minutes / 60).to_i
98
- minutes = minutes % 60
99
- days = (hours / 24).to_i
100
- hours = hours % 24
101
- milliseconds = (seconds_value % 1 * 1000).to_i
102
-
103
- parts = []
104
- parts << "#{days} days" if days > 0
105
- parts << "#{hours} hours" if hours > 0
106
- if (minutes > 0)
107
- if (minutes == 1)
108
- parts << "1 minute"
109
- else
110
- parts << "#{minutes} minutes"
111
- end
112
- end
113
- if (seconds > 0)
114
- if (seconds == 1)
115
- parts << "1 second"
116
- else
117
- parts << "#{seconds} seconds"
118
- end
119
- end
120
- #parts << "#{seconds} seconds" if seconds > 0
121
- parts << "#{milliseconds} milliseconds" if milliseconds > 0 && seconds < 1
122
-
123
- if (parts.length == 0)
124
- parts << "0 seconds"
125
- end
126
- parts.join(", ")
127
- end
128
- end
129
- end
1
+ # frozen_string_literal: true
2
+
3
+ # This module provides classes for the Makit gem.
4
+ module Makit
5
+ # Provides utilities for converting data into human-readable formats.
6
+ #
7
+ # This class handles formatting of file sizes, timestamps, durations,
8
+ # and build result summaries for display to users.
9
+ #
10
+ # @example Basic usage
11
+ # Makit::Humanize.get_humanized_size(1024) # => "1.00 KB"
12
+ # Makit::Humanize.get_humanized_duration(3661) # => "1 hour, 1 minute, 1 second"
13
+ #
14
+ class Humanize
15
+ def self.get_humanized_size(bytes, precision = 2)
16
+ units = %w[B KB MB GB TB PB]
17
+ return "0 B" if bytes.zero?
18
+
19
+ exp = (Math.log(bytes) / Math.log(1024)).to_i
20
+ exp = units.size - 1 if exp >= units.size
21
+
22
+ size = bytes.to_f / (1024 ** exp)
23
+ format("%.#{precision}f %s", size, units[exp])
24
+ end
25
+
26
+ def self.get_humanized_timestamp(timestamp)
27
+ return timestamp.strftime("%Y-%m-%d %I:%M:%S %p") if timestamp.respond_to?(:strftime)
28
+
29
+ timestamp.strftime("%Y-%m-%d %H:%M:%S")
30
+ end
31
+
32
+ def self.get_make_result_summary(make_result)
33
+ summary = "Make Result\n"
34
+ summary += " Repository: #{make_result.repository}\n"
35
+ summary += " Commit: #{make_result.commit}\n"
36
+ summary += " Branch: #{make_result.branch}\n"
37
+ summary += " Tag: #{make_result.tag}\n"
38
+ summary += " Device: #{make_result.device}\n"
39
+ summary += " Runtime Identifier: #{make_result.runtime_identifier}\n"
40
+ summary += " Initial Size: #{get_humanized_size(make_result.initial_size)}\n"
41
+ summary += " Final Size: #{get_humanized_size(make_result.final_size)}\n"
42
+ summary += " Delta Size: #{get_humanized_size(make_result.final_size - make_result.initial_size)}\n"
43
+ summary += " Commands: (#{make_result.commands.length})\n"
44
+ make_result.commands.each do |command|
45
+ details = get_command_details(command)
46
+ summary += "\n"
47
+ summary += indent_string(details, 4)
48
+ summary += "\n"
49
+ end
50
+
51
+ summary
52
+ end
53
+
54
+ def self.get_commands(commands)
55
+ message = ""
56
+ commands.each do |command|
57
+ message += Makit::Humanize.get_command_details(command)
58
+ end
59
+ message
60
+ end
61
+
62
+ def self.get_command_summary(command)
63
+ symbol = Makit::Symbols.warning
64
+ symbol = Makit::Symbols.checkmark if !command.exit_code.nil? && command.exit_code.zero?
65
+ symbol = Makit::Symbols.error if command.exit_code != 0
66
+ "#{symbol} #{command.name} #{command.arguments.join(" ")}"
67
+ end
68
+
69
+ def self.get_command_details(command)
70
+ summary = "#{get_command_summary(command)}\n"
71
+ summary += " Name: #{command.name}\n"
72
+ summary += " Arguments: #{command.arguments.join(" ")}\n"
73
+ summary += " Directory: #{command.directory}\n"
74
+ summary += " Exit Code: #{command.exit_code}\n"
75
+ if command.output.length.positive?
76
+ summary += " Output:\n"
77
+ summary += indent_string(command.output, 4)
78
+ summary += "\n"
79
+ end
80
+ if command.error.length.positive?
81
+ summary += " Error:\n"
82
+ summary += indent_string(command.error, 4)
83
+ summary += "\n"
84
+ end
85
+ summary
86
+ end
87
+
88
+ def self.indent_string(string, spaces)
89
+ string.split("\n").map { |line| (" " * spaces) + line }.join("\n")
90
+ end
91
+
92
+ def self.get_protobuf_timestamp(timestamp)
93
+ Time.at(timestamp.seconds, timestamp.nanos / 1000.0).strftime("%Y-%m-%d %H:%M:%S")
94
+ end
95
+
96
+ def self.get_protobuf_duration(duration)
97
+ total_seconds = duration.seconds + (duration.nanos / 1_000_000_000.0)
98
+ hours = (total_seconds / 3600).to_i
99
+ minutes = ((total_seconds % 3600) / 60).to_i
100
+ seconds = (total_seconds % 60).round(2)
101
+ "#{hours}h #{minutes}m #{seconds}s"
102
+ end
103
+
104
+ def self.get_humanized_duration(seconds_value)
105
+ minutes = (seconds_value / 60).to_i
106
+ seconds = (seconds_value % 60).to_i
107
+ hours = (minutes / 60).to_i
108
+ minutes %= 60
109
+ days = (hours / 24).to_i
110
+ hours %= 24
111
+ milliseconds = (seconds_value % 1 * 1000).to_i
112
+
113
+ parts = []
114
+ parts << "#{days} days" if days.positive?
115
+ parts << "#{hours} hours" if hours.positive?
116
+ if minutes.positive?
117
+ parts << if minutes == 1
118
+ "1 minute"
119
+ else
120
+ "#{minutes} minutes"
121
+ end
122
+ end
123
+ if seconds.positive?
124
+ parts << if seconds == 1
125
+ "1 second"
126
+ else
127
+ "#{seconds} seconds"
128
+ end
129
+ end
130
+ # parts << "#{seconds} seconds" if seconds > 0
131
+ parts << "#{milliseconds} milliseconds" if milliseconds.positive? && seconds < 1
132
+
133
+ parts << "0 seconds" if parts.empty?
134
+ parts.join(", ")
135
+ end
136
+ end
137
+ end
data/lib/makit/indexer.rb CHANGED
@@ -1,56 +1,47 @@
1
- # frozen_string_literal: true
2
-
3
- # This module provides classes for the Makit gem.
4
- module Makit
5
- # This class provide methods for indexing objects.
6
- #
7
- class Indexer
8
- attr_accessor :keywords_index # Hash of string key to string[] of keyword
9
- attr_accessor :protoc_json_serializer
10
-
11
- def initialize
12
- @keywords_index = Hash.new
13
- @protoc_json_serializer = Makit::Serializer::new(Makit::Proto3Formats::JSON)
14
- end
15
-
16
- def index(key, item)
17
- # item must be serializable to json
18
- keywords = []
19
- hash = JSON.parse(item.to_json)
20
- hash.each do |key, value|
21
- value = value.to_s.downcase
22
- if (value.length >= 3 && !keywords.include?(value))
23
- keywords << value
24
- end
25
- end
26
- keywords.each do |keyword|
27
- if !@keywords_index.key?(keyword)
28
- @keywords_index[keyword] = []
29
- end
30
- @keywords_index[keyword] << key unless @keywords_index[keyword].include?(key)
31
- end
32
- end
33
-
34
- def search(query)
35
- keys = []
36
- # todo, remove terms less that length of 3
37
- terms = query.downcase.split(" ").reject { |term| term.length < 3 }
38
- keywords_index.each do |key, value| #{|kvp|
39
- if (get_match_count(terms, value) == terms.length)
40
- keys << key
41
- end
42
- end
43
- keys
44
- end
45
-
46
- def get_match_count(terms, keywords)
47
- match_count = 0
48
- terms.each { |term|
49
- if (keywords.include?(term))
50
- match_count += 1
51
- end
52
- }
53
- return match_count
54
- end
55
- end # class Indexer
56
- end # module Makit
1
+ # frozen_string_literal: true
2
+
3
+ # This module provides classes for the Makit gem.
4
+ module Makit
5
+ # This class provide methods for indexing objects.
6
+ #
7
+ class Indexer
8
+ attr_accessor :keywords_index, :protoc_json_serializer # Hash of string key to string[] of keyword
9
+
10
+ def initialize
11
+ @keywords_index = {}
12
+ @protoc_json_serializer = Makit::Serializer.new(Makit::Proto3Formats::JSON)
13
+ end
14
+
15
+ def index(key, item)
16
+ # item must be serializable to json
17
+ keywords = []
18
+ hash = JSON.parse(item.to_json)
19
+ hash.each_value do |value|
20
+ value = value.to_s.downcase
21
+ keywords << value if value.length >= 3 && !keywords.include?(value)
22
+ end
23
+ keywords.each do |keyword|
24
+ @keywords_index[keyword] = [] unless @keywords_index.key?(keyword)
25
+ @keywords_index[keyword] << key unless @keywords_index[keyword].include?(key)
26
+ end
27
+ end
28
+
29
+ def search(query)
30
+ keys = []
31
+ # todo, remove terms less that length of 3
32
+ terms = query.downcase.split.reject { |term| term.length < 3 }
33
+ keywords_index.each do |key, value| # {|kvp|
34
+ keys << key if get_match_count(terms, value) == terms.length
35
+ end
36
+ keys
37
+ end
38
+
39
+ def get_match_count(terms, keywords)
40
+ match_count = 0
41
+ terms.each do |term|
42
+ match_count += 1 if keywords.include?(term)
43
+ end
44
+ match_count
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,305 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Makit
4
+ module Logging
5
+ # Configuration management for unified logging
6
+ #
7
+ # Provides schema validation, default configurations, and helper methods
8
+ # for setting up the unified file sink with various output configurations.
9
+ #
10
+ # @example Basic configuration
11
+ # config = Configuration.new(
12
+ # configurations: [
13
+ # { file: $stdout, format: :console },
14
+ # { file: "logs/app.log", format: :json }
15
+ # ]
16
+ # )
17
+ #
18
+ # @example YAML configuration
19
+ # config = Configuration.from_yaml("config/logging.yml")
20
+ #
21
+ # @example Environment-based configuration
22
+ # config = Configuration.for_environment(:production)
23
+ class Configuration
24
+ # @return [Array<Hash>] list of output configurations
25
+ attr_reader :configurations
26
+
27
+ # Initialize configuration
28
+ #
29
+ # @param configurations [Array<Hash>] list of output configurations
30
+ def initialize(configurations: [])
31
+ @configurations = configurations
32
+ end
33
+
34
+ # Create configuration from YAML file
35
+ #
36
+ # @param yaml_file [String] path to YAML configuration file
37
+ # @return [Configuration] new configuration instance
38
+ def self.from_yaml(yaml_file)
39
+ require "yaml"
40
+ data = YAML.load_file(yaml_file)
41
+ configurations = data["logging"]["sinks"] || []
42
+
43
+ # Convert string keys to symbols
44
+ configurations = configurations.map do |config|
45
+ config.transform_keys(&:to_sym)
46
+ end
47
+
48
+ new(configurations: configurations)
49
+ end
50
+
51
+ # Create configuration from JSON file
52
+ #
53
+ # @param json_file [String] path to JSON configuration file
54
+ # @return [Configuration] new configuration instance
55
+ def self.from_json(json_file)
56
+ require "json"
57
+ data = JSON.parse(File.read(json_file))
58
+ configurations = data["logging"]["sinks"] || []
59
+
60
+ # Convert string keys to symbols
61
+ configurations = configurations.map do |config|
62
+ config.transform_keys(&:to_sym)
63
+ end
64
+
65
+ new(configurations: configurations)
66
+ end
67
+
68
+ # Create environment-specific configuration
69
+ #
70
+ # @param environment [Symbol] environment name (:development, :production, :test)
71
+ # @return [Configuration] new configuration instance
72
+ def self.for_environment(environment)
73
+ case environment
74
+ when :development
75
+ development_config
76
+ when :production
77
+ production_config
78
+ when :test
79
+ test_config
80
+ when :ci
81
+ ci_config
82
+ else
83
+ default_config
84
+ end
85
+ end
86
+
87
+ # Get default configuration
88
+ #
89
+ # @return [Configuration] default configuration
90
+ def self.default_config
91
+ new(
92
+ configurations: [
93
+ { file: $stdout, format: :console },
94
+ { file: "artifacts/makit.log", format: :json, append: true },
95
+ ],
96
+ )
97
+ end
98
+
99
+ # Get development configuration
100
+ #
101
+ # @return [Configuration] development configuration
102
+ def self.development_config
103
+ new(
104
+ configurations: [
105
+ {
106
+ file: $stdout,
107
+ format: :console,
108
+ show_timestamp: true,
109
+ show_level: true,
110
+ },
111
+ {
112
+ file: "logs/development.log",
113
+ format: :text,
114
+ append: true,
115
+ include_context: true,
116
+ },
117
+ {
118
+ file: "logs/debug.log",
119
+ format: :json,
120
+ append: true,
121
+ min_level: :debug,
122
+ },
123
+ ],
124
+ )
125
+ end
126
+
127
+ # Get production configuration
128
+ #
129
+ # @return [Configuration] production configuration
130
+ def self.production_config
131
+ new(
132
+ configurations: [
133
+ {
134
+ file: "logs/application.log",
135
+ format: :json,
136
+ append: true,
137
+ include_metadata: true,
138
+ rotation: { max_size: "100MB", max_files: 10 },
139
+ },
140
+ {
141
+ file: "logs/errors.log",
142
+ format: :json,
143
+ append: true,
144
+ min_level: :error,
145
+ include_metadata: true,
146
+ },
147
+ ],
148
+ )
149
+ end
150
+
151
+ # Get test configuration
152
+ #
153
+ # @return [Configuration] test configuration
154
+ def self.test_config
155
+ new(
156
+ configurations: [
157
+ {
158
+ file: "logs/test.log",
159
+ format: :text,
160
+ append: false,
161
+ include_context: true,
162
+ },
163
+ ],
164
+ )
165
+ end
166
+
167
+ # Get CI/CD configuration
168
+ #
169
+ # @return [Configuration] CI configuration
170
+ def self.ci_config
171
+ new(
172
+ configurations: [
173
+ {
174
+ file: $stdout,
175
+ format: :console,
176
+ show_level: true,
177
+ },
178
+ {
179
+ file: "artifacts/build.log",
180
+ format: :json,
181
+ append: true,
182
+ include_metadata: true,
183
+ },
184
+ ],
185
+ )
186
+ end
187
+
188
+ # Validate configuration
189
+ #
190
+ # @return [Boolean] true if valid
191
+ # @raise [ArgumentError] if configuration is invalid
192
+ def validate!
193
+ raise ArgumentError, "configurations must be an array" unless @configurations.is_a?(Array)
194
+ raise ArgumentError, "at least one configuration is required" if @configurations.empty?
195
+
196
+ @configurations.each_with_index do |config, index|
197
+ validate_single_configuration(config, index)
198
+ end
199
+
200
+ true
201
+ end
202
+
203
+ # Check if configuration is valid
204
+ #
205
+ # @return [Boolean] true if valid
206
+ def valid?
207
+ validate!
208
+ true
209
+ rescue ArgumentError
210
+ false
211
+ end
212
+
213
+ # Get configuration as hash
214
+ #
215
+ # @return [Hash] configuration as hash
216
+ def to_hash
217
+ {
218
+ logging: {
219
+ sinks: @configurations,
220
+ },
221
+ }
222
+ end
223
+
224
+ # Save configuration to YAML file
225
+ #
226
+ # @param yaml_file [String] path to YAML file
227
+ # @return [void]
228
+ def save_yaml(yaml_file)
229
+ require "yaml"
230
+ FileUtils.mkdir_p(File.dirname(yaml_file))
231
+ File.write(yaml_file, to_hash.to_yaml)
232
+ end
233
+
234
+ # Save configuration to JSON file
235
+ #
236
+ # @param json_file [String] path to JSON file
237
+ # @return [void]
238
+ def save_json(json_file)
239
+ require "json"
240
+ FileUtils.mkdir_p(File.dirname(json_file))
241
+ File.write(json_file, JSON.pretty_generate(to_hash))
242
+ end
243
+
244
+ private
245
+
246
+ # Validate a single configuration
247
+ #
248
+ # @param config [Hash] configuration to validate
249
+ # @param index [Integer] configuration index for error messages
250
+ # @raise [ArgumentError] if configuration is invalid
251
+ def validate_single_configuration(config, index)
252
+ raise ArgumentError, "configuration #{index} must be a hash" unless config.is_a?(Hash)
253
+
254
+ required_keys = [:file, :format]
255
+ missing_keys = required_keys - config.keys
256
+ raise ArgumentError, "configuration #{index} missing required keys: #{missing_keys.join(", ")}" unless missing_keys.empty?
257
+
258
+ # Validate file
259
+ file = config[:file]
260
+ unless file.is_a?(String) || file.is_a?(IO) || file.respond_to?(:write)
261
+ raise ArgumentError, "configuration #{index} file must be a String path, IO object, or writable object"
262
+ end
263
+
264
+ # Validate format
265
+ format = config[:format]
266
+ unless format.is_a?(Symbol) || format.is_a?(String)
267
+ raise ArgumentError, "configuration #{index} format must be a Symbol or String"
268
+ end
269
+
270
+ # Validate log levels if specified
271
+ if config[:min_level] && !valid_log_level?(config[:min_level])
272
+ raise ArgumentError, "configuration #{index} min_level must be a valid log level"
273
+ end
274
+
275
+ if config[:max_level] && !valid_log_level?(config[:max_level])
276
+ raise ArgumentError, "configuration #{index} max_level must be a valid log level"
277
+ end
278
+
279
+ # Validate rotation if specified
280
+ if config[:rotation] && !valid_rotation_config?(config[:rotation])
281
+ raise ArgumentError, "configuration #{index} rotation must be a valid rotation configuration"
282
+ end
283
+ end
284
+
285
+ # Check if log level is valid
286
+ #
287
+ # @param level [Symbol] log level to check
288
+ # @return [Boolean] true if valid
289
+ def valid_log_level?(level)
290
+ %i[debug info warn error fatal success].include?(level)
291
+ end
292
+
293
+ # Check if rotation configuration is valid
294
+ #
295
+ # @param rotation [Hash] rotation configuration
296
+ # @return [Boolean] true if valid
297
+ def valid_rotation_config?(rotation)
298
+ return false unless rotation.is_a?(Hash)
299
+
300
+ valid_keys = [:max_size, :max_files, :compress, :pattern]
301
+ rotation.keys.all? { |key| valid_keys.include?(key) }
302
+ end
303
+ end
304
+ end
305
+ end