crystalruby 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/README.md +391 -195
  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 +33 -4
  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
- 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})
37
+ redirect_output = " > /dev/null " unless verbose
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