crystalruby 0.2.3 → 0.3.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/README.md +389 -193
  4. data/Rakefile +4 -3
  5. data/crystalruby.gemspec +2 -2
  6. data/exe/crystalruby +1 -0
  7. data/lib/crystalruby/adapter.rb +131 -65
  8. data/lib/crystalruby/arc_mutex.rb +47 -0
  9. data/lib/crystalruby/compilation.rb +32 -3
  10. data/lib/crystalruby/config.rb +41 -37
  11. data/lib/crystalruby/function.rb +211 -68
  12. data/lib/crystalruby/library.rb +153 -48
  13. data/lib/crystalruby/reactor.rb +40 -23
  14. data/lib/crystalruby/source_reader.rb +86 -0
  15. data/lib/crystalruby/template.rb +16 -5
  16. data/lib/crystalruby/templates/function.cr +11 -10
  17. data/lib/crystalruby/templates/index.cr +53 -66
  18. data/lib/crystalruby/templates/inline_chunk.cr +1 -1
  19. data/lib/crystalruby/templates/ruby_interface.cr +34 -0
  20. data/lib/crystalruby/templates/top_level_function.cr +62 -0
  21. data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
  22. data/lib/crystalruby/typebuilder.rb +11 -55
  23. data/lib/crystalruby/typemaps.rb +92 -67
  24. data/lib/crystalruby/types/concerns/allocator.rb +80 -0
  25. data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
  26. data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
  27. data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
  28. data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
  29. data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
  30. data/lib/crystalruby/types/fixed_width/tagged_union.rb +109 -0
  31. data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
  32. data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
  33. data/lib/crystalruby/types/fixed_width.cr +138 -0
  34. data/lib/crystalruby/types/fixed_width.rb +205 -0
  35. data/lib/crystalruby/types/primitive.cr +21 -0
  36. data/lib/crystalruby/types/primitive.rb +117 -0
  37. data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
  38. data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
  39. data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
  40. data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
  41. data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
  42. data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
  43. data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
  44. data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
  45. data/lib/crystalruby/types/primitive_types/time.cr +35 -0
  46. data/lib/crystalruby/types/primitive_types/time.rb +25 -0
  47. data/lib/crystalruby/types/type.cr +64 -0
  48. data/lib/crystalruby/types/type.rb +239 -30
  49. data/lib/crystalruby/types/variable_width/array.cr +74 -0
  50. data/lib/crystalruby/types/variable_width/array.rb +88 -0
  51. data/lib/crystalruby/types/variable_width/hash.cr +146 -0
  52. data/lib/crystalruby/types/variable_width/hash.rb +117 -0
  53. data/lib/crystalruby/types/variable_width/string.cr +36 -0
  54. data/lib/crystalruby/types/variable_width/string.rb +18 -0
  55. data/lib/crystalruby/types/variable_width.cr +23 -0
  56. data/lib/crystalruby/types/variable_width.rb +46 -0
  57. data/lib/crystalruby/types.rb +32 -13
  58. data/lib/crystalruby/version.rb +2 -2
  59. data/lib/crystalruby.rb +13 -6
  60. metadata +41 -19
  61. data/lib/crystalruby/types/array.rb +0 -15
  62. data/lib/crystalruby/types/bool.rb +0 -3
  63. data/lib/crystalruby/types/hash.rb +0 -17
  64. data/lib/crystalruby/types/named_tuple.rb +0 -28
  65. data/lib/crystalruby/types/nil.rb +0 -3
  66. data/lib/crystalruby/types/numbers.rb +0 -5
  67. data/lib/crystalruby/types/string.rb +0 -3
  68. data/lib/crystalruby/types/symbol.rb +0 -3
  69. data/lib/crystalruby/types/time.rb +0 -8
  70. data/lib/crystalruby/types/tuple.rb +0 -17
  71. data/lib/crystalruby/types/type_serializer/json.rb +0 -41
  72. data/lib/crystalruby/types/type_serializer.rb +0 -37
  73. data/lib/crystalruby/types/typedef.rb +0 -57
  74. data/lib/crystalruby/types/union_type.rb +0 -43
  75. data/lib/module.rb +0 -3
data/Rakefile CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "minitest/test_task"
5
-
6
- Minitest::TestTask.create
7
4
 
8
5
  require "rubocop/rake_task"
9
6
 
10
7
  RuboCop::RakeTask.new
11
8
 
12
9
  task default: %i[test rubocop]
10
+
11
+ task :test do
12
+ require_relative "test/test_all"
13
+ end
data/crystalruby.gemspec CHANGED
@@ -4,7 +4,7 @@ require_relative "lib/crystalruby/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "crystalruby"
7
- spec.version = Crystalruby::VERSION
7
+ spec.version = CrystalRuby::VERSION
8
8
  spec.authors = ["Wouter Coppieters"]
9
9
  spec.email = ["wc@pico.net.nz"]
10
10
 
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_dependency "digest"
36
36
  spec.add_dependency "ffi"
37
37
  spec.add_dependency "fileutils"
38
- spec.add_dependency "method_source"
38
+ spec.add_dependency "syntax_tree"
39
39
  # For more information and examples about making a new gem, check out our
40
40
  # guide at: https://bundler.io/guides/creating_gem.html
41
41
  end
data/exe/crystalruby CHANGED
@@ -10,6 +10,7 @@ def init
10
10
  # crystalruby configuration file
11
11
  crystal_src_dir: "./crystalruby"
12
12
  crystal_codegen_dir: "generated"
13
+ crystal_missing_ignore: false
13
14
  log_level: "info"
14
15
  single_thread_mode: false
15
16
  debug: true
@@ -8,16 +8,16 @@ module CrystalRuby
8
8
  #
9
9
  # E.g.
10
10
  #
11
- # crystalize [a: :int32, b: :int32] => :int32
12
- # def add(a, b)
11
+ # crystalize :int32
12
+ # def add(a: :int32, b: :int32)
13
13
  # a + b
14
14
  # end
15
15
  #
16
16
  # Pass `raw: true` to pass Raw crystal code to the compiler as a string instead.
17
17
  # (Useful for cases where the Crystal method body is not valid Ruby)
18
18
  # E.g.
19
- # crystalize raw: true [a: :int32, b: :int32] => :int32
20
- # def add(a, b)
19
+ # crystalize :int32, raw: true
20
+ # def add(a: :int32, b: :int32)
21
21
  # <<~CRYSTAL
22
22
  # a + b
23
23
  # CRYSTAL
@@ -30,73 +30,135 @@ module CrystalRuby
30
30
  #
31
31
  # Pass lib: "name_of_lib" to compile Crystal code into several distinct libraries.
32
32
  # This can help keep compilation times low, by packaging your Crystal code into separate shared objects.
33
- def crystalize(raw: false, async: false, lib: "crystalruby", **options, &block)
34
- (args,), returns = options.first || [[], :void]
35
- args ||= {}
36
- raise "Arguments should be of the form name: :type. Got #{args}" unless args.is_a?(Hash)
37
-
38
- @crystalize_next = {
39
- raw: raw,
40
- async: async,
41
- args: args,
42
- returns: returns,
43
- block: block,
44
- lib: lib
45
- }
33
+ # @param returns The return type of the method. Optional (defaults to :void).
34
+ # @param [Hash] options The options hash.
35
+ # @option options [Boolean] :raw (false) Pass raw Crystal code to the compiler as a string.
36
+ # @option options [Boolean] :async (false) Mark the method as async (allows multiplexing).
37
+ # @option options [String] :lib ("crystalruby") The name of the library to compile the Crystal code into.
38
+ # @option options [Proc] :block An optional wrapper Ruby block that wraps around any invocations of the crystal code
39
+ def crystalize( returns=:void, raw: false, async: false, lib: "crystalruby", &block)
40
+ (self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
41
+ @crystalize_next = {
42
+ raw: raw,
43
+ async: async,
44
+ returns: returns,
45
+ block: block,
46
+ lib: lib
47
+ }
48
+ end
46
49
  end
47
50
 
48
- # This method provides a useful DSL for defining Crystal types in pure Ruby.
49
- # These types can not be passed directly to Ruby, and must be serialized as either:
50
- # JSON or
51
- # C-Structures (WIP)
52
- #
53
- # See #json for an example of how to define arguments or return types for complex objects.
54
- # E.g.
55
- #
56
- # MyType = crtype{ Int32 | Hash(String, Array(Bool) | Float65 | Nil) }
57
- def crtype(&block)
58
- TypeBuilder.with_injected_type_dsl(self) do
59
- TypeBuilder.build(&block)
51
+ # Exposes a Ruby method to one or more Crystal libraries.
52
+ # Type annotations follow the same rules as the `crystalize` method, but are
53
+ # applied in reverse.
54
+ # @param returns The return type of the method. Optional (defaults to :void).
55
+ # @param [Hash] options The options hash.
56
+ # @option options [Boolean] :raw (false) Pass raw Crystal code to the compiler as a string.
57
+ # @option options [String] :libs (["crystalruby"]) The name of the Crystal librarie(s) to expose the Ruby code to.
58
+ def expose_to_crystal( returns=:void, libs: ["crystalruby"])
59
+ (self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
60
+ @expose_next_to_crystal = {
61
+ returns: returns,
62
+ libs: libs
63
+ }
60
64
  end
61
65
  end
62
66
 
63
- # Use the json{} helper for defining complex method arguments or return types
64
- # that should be serialized to and from Crystal using JSON. (This conversion is applied automatically)
65
- #
66
- # E.g.
67
- # crystalize [a: json{ Int32 | Float64 | Nil } ] => NamedStruct(result: Int32 | Float64 | Nil)
68
- def json(&block)
69
- crtype(&block).serialize_as(:json)
67
+ # Define a shard dependency
68
+ # This dependency will be automatically injected into the shard.yml file for
69
+ # the given library and installed upon compile if it is not already installed.
70
+ def shard(shard_name, lib: 'crystalruby', **opts)
71
+ CrystalRuby::Library[lib].require_shard(shard_name, **opts)
72
+ end
73
+
74
+ # Use this method to define inline Crystal code that does not need to be bound to a Ruby method.
75
+ # This is useful for defining classes, modules, performing set-up tasks etc.
76
+ # See: docs for .crystalize to understand the `raw` and `lib` parameters.
77
+ def crystal(raw: false, lib: "crystalruby", &block)
78
+ inline_crystal_body = respond_to?(:name) ? Template::InlineChunk.render(
79
+ {
80
+ module_name: name,
81
+ body: SourceReader.extract_source_from_proc(block, raw: raw),
82
+ mod_or_class: self.kind_of?(Class) && self < Types::Type ? "class" : "module",
83
+ superclass: self.kind_of?(Class) && self < Types::Type ? "< #{self.crystal_supertype}" : ""
84
+ }) :
85
+ SourceReader.extract_source_from_proc(block, raw: raw)
86
+
87
+ CrystalRuby::Library[lib].crystalize_chunk(
88
+ self,
89
+ Digest::MD5.hexdigest(inline_crystal_body),
90
+ inline_crystal_body
91
+ )
92
+ end
93
+
94
+
95
+ # This method provides a useful DSL for defining Crystal types in pure Ruby
96
+ # MyType = CRType{ Int32 | Hash(String, Array(Bool) | Float65 | Nil) }
97
+ # @param [Proc] block The block within which we build the type definition.
98
+ def CRType(&block)
99
+ TypeBuilder.build_from_source(block, context: self)
70
100
  end
71
101
 
102
+ private
103
+
72
104
  # We trigger attaching of crystalized instance methods here.
73
105
  # If a method is added after a crystalize annotation we assume it's the target of the crystalize annotation.
106
+ # @param [Symbol] method_name The name of the method being added.
74
107
  def method_added(method_name)
75
- define_crystalized_method(method_name, instance_method(method_name)) if @crystalize_next
108
+ define_crystalized_method(instance_method(method_name)) if should_crystalize_next?
109
+ expose_ruby_method_to_crystal(instance_method(method_name)) if should_expose_next?
76
110
  super
77
111
  end
78
112
 
79
113
  # We trigger attaching of crystalized class methods here.
80
114
  # If a method is added after a crystalize annotation we assume it's the target of the crystalize annotation.
115
+ # @note This method is called when a method is added to the singleton class of the object.
116
+ # @param [Symbol] method_name The name of the method being added.
81
117
  def singleton_method_added(method_name)
82
- define_crystalized_method(method_name, singleton_method(method_name)) if @crystalize_next
118
+ define_crystalized_method(singleton_method(method_name)) if should_crystalize_next?
119
+ expose_ruby_method_to_crystal(singleton_method(method_name)) if should_expose_next?
83
120
  super
84
121
  end
85
122
 
86
- # Use this method to define inline Crystal code that does not need to be bound to a Ruby method.
87
- # This is useful for defining classes, modules, performing set-up tasks etc.
88
- # See: docs for .crystalize to understand the `raw` and `lib` parameters.
89
- def crystal(raw: false, lib: "crystalruby", &block)
90
- inline_crystal_body = Template::InlineChunk.render(
91
- {
92
- module_name: name, body: extract_source(block, raw: raw)
93
- }
94
- )
95
- CrystalRuby::Library[lib].crystalize_chunk(
96
- self,
97
- Digest::MD5.hexdigest(inline_crystal_body),
98
- inline_crystal_body
99
- )
123
+ # Helper method to determine if the next method added should be crystalized.
124
+ # @return [Boolean] True if the next method added should be crystalized.
125
+ def should_crystalize_next?
126
+ defined?(@crystalize_next) && @crystalize_next
127
+ end
128
+
129
+ # Helper method to determine if the next method added should be exposed to Crystal libraries.
130
+ # @return [Boolean] True if the next method added should be exposed.
131
+ def should_expose_next?
132
+ defined?(@expose_next_to_crystal) && @expose_next_to_crystal
133
+ end
134
+
135
+ # This is where we extract the Ruby method metadata and invoke the Crystal::Library functionality
136
+ # to compile a stub for the Ruby method into the Crystal library.
137
+ def expose_ruby_method_to_crystal(method)
138
+ returns, libs = @expose_next_to_crystal.values_at(:returns, :libs)
139
+ @expose_next_to_crystal = nil
140
+
141
+ args, source = SourceReader.extract_args_and_source_from_method(method)
142
+ returns = args.delete(:returns) if args[:returns] && returns == :void
143
+ args[:__yield_to] = args.delete(:yield) if args[:yield]
144
+ src = <<~RUBY
145
+ def #{method.name} (#{(args.keys - [:__yield_to]).join(", ")})
146
+ #{source}
147
+ end
148
+ RUBY
149
+
150
+ owner = method.owner.singleton_class? ? method.owner.attached_object : method.owner
151
+ owner.class_eval(src)
152
+ owner.instance_eval(src) unless method.kind_of?(UnboundMethod) && method.owner.ancestors.include?(CrystalRuby::Types::Type)
153
+ method = owner.send(method.kind_of?(UnboundMethod) ? :instance_method : :method, method.name)
154
+
155
+ libs.each do |lib|
156
+ CrystalRuby::Library[lib].expose_method(
157
+ method,
158
+ args,
159
+ returns,
160
+ )
161
+ end
100
162
  end
101
163
 
102
164
  # We attach crystalized class methods here.
@@ -105,29 +167,33 @@ module CrystalRuby
105
167
  # - Overwriting the method and class methods by the same name in the caller.
106
168
  # - Lazily triggering compilation and attachment of the Ruby method to the Crystal code.
107
169
  # - We also optionally prepend a block (if given) to the owner, to allow Ruby code to wrap around Crystal code.
108
- def define_crystalized_method(method_name, method)
109
- CrystalRuby.log_debug("Defining crystalized method #{name}.#{method_name}")
170
+ # @param [Symbol] method_name The name of the method being added.
171
+ # @param [UnboundMethod] method The method being added.
172
+ def define_crystalized_method(method)
173
+ CrystalRuby.log_debug("Defining crystalized method #{name}.#{method.name}")
110
174
 
111
- args, returns, block, async, lib, raw = @crystalize_next.values_at(:args, :returns, :block, :async, :lib, :raw)
175
+ returns, block, async, lib, raw = @crystalize_next.values_at(:returns, :block, :async, :lib, :raw)
112
176
  @crystalize_next = nil
113
177
 
178
+ args, source = SourceReader.extract_args_and_source_from_method(method, raw: raw)
179
+
180
+ # We can safely claim the `yield` argument name for typing the yielded block
181
+ # because this is an illegal identifier in Crystal anyway.
182
+ args[:__yield_to] = args.delete(:yield) if args[:yield]
183
+ returns = args.delete(:returns) if args[:returns] && returns == :void
184
+
114
185
  CrystalRuby::Library[lib].crystalize_method(
115
186
  method,
116
187
  args,
117
188
  returns,
118
- extract_source(method, raw: raw),
189
+ source,
119
190
  async,
120
191
  &block
121
192
  )
122
193
  end
123
-
124
- # Extract Ruby source to serve as Crystal code directly.
125
- # If it's a raw method, we'll strip the string delimiters at either end of the definition.
126
- # We need to clear the MethodSource cache here to allow for code reloading.
127
- def extract_source(method_or_block, raw: false)
128
- method_or_block.source.lines[raw ? 2...-2 : 1...-1].join("\n").tap do
129
- MethodSource.instance_variable_get(:@lines_for_file).delete(method_or_block.source_location[0])
130
- end
131
- end
132
194
  end
133
195
  end
196
+
197
+ Module.prepend(CrystalRuby::Adapter)
198
+ BasicObject.prepend(CrystalRuby::Adapter)
199
+ BasicObject.singleton_class.prepend(CrystalRuby::Adapter)
@@ -0,0 +1,47 @@
1
+ module CrystalRuby
2
+ module LibC
3
+ extend FFI::Library
4
+ ffi_lib "c"
5
+ class PThreadMutexT < FFI::Struct
6
+ layout :__align, :int64, :__size, :char, 40
7
+ end
8
+
9
+ attach_function :pthread_mutex_init, [PThreadMutexT.by_ref, :pointer], :int
10
+ attach_function :pthread_mutex_lock, [PThreadMutexT.by_ref], :int
11
+ attach_function :pthread_mutex_unlock, [PThreadMutexT.by_ref], :int
12
+ end
13
+
14
+ class ArcMutex
15
+ def phtread_mutex
16
+ @phtread_mutex ||= init_mutex!
17
+ end
18
+
19
+ def synchronize
20
+ lock
21
+ yield
22
+ unlock
23
+ end
24
+
25
+ def to_ptr
26
+ phtread_mutex.pointer
27
+ end
28
+
29
+ def init_mutex!
30
+ mutex = LibC::PThreadMutexT.new
31
+ res = LibC.pthread_mutex_init(mutex, nil)
32
+ raise "Failed to initialize mutex" unless res.zero?
33
+
34
+ mutex
35
+ end
36
+
37
+ def lock
38
+ res = LibC.pthread_mutex_lock(phtread_mutex)
39
+ raise "Failed to lock mutex" unless res.zero?
40
+ end
41
+
42
+ def unlock
43
+ res = LibC.pthread_mutex_unlock(phtread_mutex)
44
+ raise "Failed to unlock mutex" unless res.zero?
45
+ end
46
+ end
47
+ end
@@ -1,10 +1,18 @@
1
1
  require "tmpdir"
2
2
  require "shellwords"
3
+ require "timeout"
3
4
 
4
5
  module CrystalRuby
5
6
  module Compilation
6
7
  class CompilationFailedError < StandardError; end
7
8
 
9
+ # Simple wrapper around invocation of the Crystal compiler
10
+ # @param src [String] path to the source file
11
+ # @param lib [String] path to the library file
12
+ # @param verbose [Boolean] whether to print the compiler output
13
+ # @param debug [Boolean] whether to compile in debug mode
14
+ # @raise [CompilationFailedError] if the compilation fails
15
+ # @return [void]
8
16
  def self.compile!(
9
17
  src:,
10
18
  lib:,
@@ -13,15 +21,36 @@ module CrystalRuby
13
21
  )
14
22
  compile_command = build_compile_command(verbose: verbose, debug: debug, lib: lib, src: src)
15
23
  CrystalRuby.log_debug "Compiling Crystal code #{verbose ? ": #{compile_command}" : ""}"
16
- raise CompilationFailedError, "Compilation failed" unless system(compile_command)
24
+ IO.popen(compile_command, chdir: File.dirname(src), &:read)
25
+ raise CompilationFailedError, "Compilation failed in #{src}" unless $?&.success?
17
26
  end
18
27
 
28
+ # Builds the command to compile the Crystal source file
29
+ # @param verbose [Boolean] whether to print the compiler output
30
+ # @param debug [Boolean] whether to compile in debug mode
31
+ # @param lib [String] path to the library file
32
+ # @param src [String] path to the source file
33
+ # @return [String] the command to compile the Crystal source file
19
34
  def self.build_compile_command(verbose:, debug:, lib:, src:)
20
35
  verbose_flag = verbose ? "--verbose --progress" : ""
21
36
  debug_flag = debug ? "" : "--release --no-debug"
22
37
  redirect_output = " > /dev/null " unless verbose
23
- lib, src, lib_dir = [lib, src, File.dirname(src)].map(&Shellwords.method(:escape))
24
- %(cd #{lib_dir} && crystal build #{verbose_flag} #{debug_flag} --single-module --link-flags "-shared" -o #{lib} #{src}#{redirect_output})
38
+ lib, src = [lib, src].map(&Shellwords.method(:escape))
39
+ %(crystal build #{verbose_flag} #{debug_flag} --single-module --link-flags "-shared" -o #{lib} #{src}#{redirect_output})
40
+ end
41
+
42
+ # Trigger the shards install command in the given source directory
43
+ def self.install_shards!(src_dir)
44
+ CrystalRuby.log_debug "Running shards install inside #{src_dir}"
45
+ output = IO.popen("shards update", chdir: src_dir, &:read)
46
+ CrystalRuby.log_debug output if CrystalRuby.config.verbose
47
+ raise CompilationFailedError, "Shards install failed" unless $?&.success?
48
+ end
49
+
50
+ # Return whether the shards check command succeeds in the given source directory
51
+ def self.shard_check?(src_dir)
52
+ IO.popen("shards check", chdir: src_dir, &:read)
53
+ $?&.success?
25
54
  end
26
55
  end
27
56
  end
@@ -17,59 +17,63 @@ module CrystalRuby
17
17
  # - CrystalRuby.configure block
18
18
  class Configuration
19
19
  include Singleton
20
- attr_accessor :debug, :verbose, :logger, :colorize_log_output, :single_thread_mode
20
+ attr_accessor :debug, :verbose, :logger, :colorize_log_output,
21
+ :single_thread_mode, :crystal_missing_ignore
21
22
 
22
23
  def initialize
23
- @debug = true
24
24
  @paths_cache = {}
25
- config = File.exist?("crystalruby.yaml") && begin
25
+ config = read_config || {}
26
+ @crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby")
27
+ @crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
28
+ @crystal_project_root = config.fetch("crystal_project_root", Pathname.pwd)
29
+ @crystal_missing_ignore = config.fetch("crystal_missing_ignore", false)
30
+ @debug = config.fetch("debug", false)
31
+ @verbose = config.fetch("verbose", false)
32
+ @single_thread_mode = config.fetch("single_thread_mode", false)
33
+ @colorize_log_output = config.fetch("colorize_log_output", false)
34
+ @log_level = config.fetch("log_level", ENV.fetch("CRYSTALRUBY_LOG_LEVEL", "info"))
35
+ @logger = Logger.new($stdout)
36
+ @logger.level = Logger.const_get(@log_level.to_s.upcase)
37
+ end
38
+
39
+ def file_config
40
+ @file_config ||= File.exist?("crystalruby.yaml") && begin
26
41
  YAML.safe_load(IO.read("crystalruby.yaml"))
27
42
  rescue StandardError
28
43
  nil
29
44
  end || {}
30
- @crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby")
31
- @crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
32
- @crystal_project_root = config.fetch("crystal_project_root", Pathname.pwd)
33
- @debug = config.fetch("debug", true)
34
- @verbose = config.fetch("verbose", false)
35
- @single_thread_mode = config.fetch("single_thread_mode", false)
36
- @colorize_log_output = config.fetch("colorize_log_output", false)
37
- @log_level = config.fetch("log_level", ENV.fetch("CRYSTALRUBY_LOG_LEVEL", "info"))
38
- @logger = Logger.new(STDOUT)
39
- @logger.level = Logger.const_get(@log_level.to_s.upcase)
40
45
  end
41
46
 
42
- %w[crystal_project_root].each do |method_name|
43
- define_method(method_name) do
44
- @paths_cache[method_name] ||= Pathname.new(instance_variable_get(:"@#{method_name}"))
47
+ def self.define_directory_accessors!(parent, directory_node)
48
+ case directory_node
49
+ when Array then directory_node.each(&method(:define_directory_accessors!).curry[parent])
50
+ when Hash
51
+ directory_node.each do |par, children|
52
+ define_directory_accessors!(parent, par)
53
+ define_directory_accessors!(par, children)
54
+ end
55
+ else
56
+ define_method(directory_node) do
57
+ @paths_cache[directory_node] ||= Pathname.new(instance_variable_get(:"@#{directory_node}"))
58
+ end
59
+ define_method("#{directory_node}_abs") do
60
+ @paths_cache["#{directory_node}_abs"] ||= parent ? send("#{parent}_abs") / Pathname.new(instance_variable_get(:"@#{directory_node}")) : send(directory_node)
61
+ end
45
62
  end
46
63
  end
47
64
 
48
- %w[crystal_codegen_dir].each do |method_name|
49
- abs_method_name = "#{method_name}_abs"
50
- define_method(abs_method_name) do
51
- @paths_cache[abs_method_name] ||= crystal_src_dir_abs / instance_variable_get(:"@#{method_name}")
52
- end
65
+ define_directory_accessors!(nil, { crystal_project_root: { crystal_src_dir: [:crystal_codegen_dir] } })
53
66
 
54
- define_method(method_name) do
55
- @paths_cache[method_name] ||= Pathname.new instance_variable_get(:"@#{method_name}")
56
- end
67
+ def log_level=(level)
68
+ @logger.level = Logger.const_get(@log_level = level.to_s.upcase)
57
69
  end
58
70
 
59
- %w[crystal_src_dir].each do |method_name|
60
- abs_method_name = "#{method_name}_abs"
61
- define_method(abs_method_name) do
62
- @paths_cache[abs_method_name] ||= crystal_project_root / instance_variable_get(:"@#{method_name}")
63
- end
71
+ private
64
72
 
65
- define_method(method_name) do
66
- @paths_cache[method_name] ||= Pathname.new instance_variable_get(:"@#{method_name}")
67
- end
68
- end
69
-
70
- def log_level=(level)
71
- @log_level = level
72
- @logger.level = Logger.const_get(level.to_s.upcase)
73
+ def read_config
74
+ YAML.safe_load(IO.read("crystalruby.yaml"))
75
+ rescue
76
+ {}
73
77
  end
74
78
  end
75
79