crystalruby 0.2.3 → 0.3.1

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/Dockerfile +23 -2
  4. data/README.md +395 -198
  5. data/Rakefile +4 -3
  6. data/crystalruby.gemspec +2 -2
  7. data/examples/adder/adder.rb +1 -1
  8. data/exe/crystalruby +1 -0
  9. data/lib/crystalruby/adapter.rb +143 -73
  10. data/lib/crystalruby/arc_mutex.rb +47 -0
  11. data/lib/crystalruby/compilation.rb +32 -3
  12. data/lib/crystalruby/config.rb +41 -37
  13. data/lib/crystalruby/function.rb +216 -73
  14. data/lib/crystalruby/library.rb +157 -51
  15. data/lib/crystalruby/reactor.rb +63 -44
  16. data/lib/crystalruby/source_reader.rb +92 -0
  17. data/lib/crystalruby/template.rb +16 -5
  18. data/lib/crystalruby/templates/function.cr +11 -10
  19. data/lib/crystalruby/templates/index.cr +53 -66
  20. data/lib/crystalruby/templates/inline_chunk.cr +1 -1
  21. data/lib/crystalruby/templates/ruby_interface.cr +34 -0
  22. data/lib/crystalruby/templates/top_level_function.cr +62 -0
  23. data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
  24. data/lib/crystalruby/typebuilder.rb +11 -55
  25. data/lib/crystalruby/typemaps.rb +92 -67
  26. data/lib/crystalruby/types/concerns/allocator.rb +80 -0
  27. data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
  28. data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
  29. data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
  30. data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
  31. data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
  32. data/lib/crystalruby/types/fixed_width/tagged_union.rb +113 -0
  33. data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
  34. data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
  35. data/lib/crystalruby/types/fixed_width.cr +138 -0
  36. data/lib/crystalruby/types/fixed_width.rb +205 -0
  37. data/lib/crystalruby/types/primitive.cr +21 -0
  38. data/lib/crystalruby/types/primitive.rb +117 -0
  39. data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
  40. data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
  41. data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
  42. data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
  43. data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
  44. data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
  45. data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
  46. data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
  47. data/lib/crystalruby/types/primitive_types/time.cr +35 -0
  48. data/lib/crystalruby/types/primitive_types/time.rb +25 -0
  49. data/lib/crystalruby/types/type.cr +64 -0
  50. data/lib/crystalruby/types/type.rb +249 -30
  51. data/lib/crystalruby/types/variable_width/array.cr +74 -0
  52. data/lib/crystalruby/types/variable_width/array.rb +88 -0
  53. data/lib/crystalruby/types/variable_width/hash.cr +146 -0
  54. data/lib/crystalruby/types/variable_width/hash.rb +117 -0
  55. data/lib/crystalruby/types/variable_width/string.cr +36 -0
  56. data/lib/crystalruby/types/variable_width/string.rb +18 -0
  57. data/lib/crystalruby/types/variable_width.cr +23 -0
  58. data/lib/crystalruby/types/variable_width.rb +46 -0
  59. data/lib/crystalruby/types.rb +32 -13
  60. data/lib/crystalruby/version.rb +2 -2
  61. data/lib/crystalruby.rb +13 -6
  62. metadata +42 -22
  63. data/lib/crystalruby/types/array.rb +0 -15
  64. data/lib/crystalruby/types/bool.rb +0 -3
  65. data/lib/crystalruby/types/hash.rb +0 -17
  66. data/lib/crystalruby/types/named_tuple.rb +0 -28
  67. data/lib/crystalruby/types/nil.rb +0 -3
  68. data/lib/crystalruby/types/numbers.rb +0 -5
  69. data/lib/crystalruby/types/string.rb +0 -3
  70. data/lib/crystalruby/types/symbol.rb +0 -3
  71. data/lib/crystalruby/types/time.rb +0 -8
  72. data/lib/crystalruby/types/tuple.rb +0 -17
  73. data/lib/crystalruby/types/type_serializer/json.rb +0 -41
  74. data/lib/crystalruby/types/type_serializer.rb +0 -37
  75. data/lib/crystalruby/types/typedef.rb +0 -57
  76. data/lib/crystalruby/types/union_type.rb +0 -43
  77. 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 "prism"
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
@@ -1,7 +1,7 @@
1
1
  require "crystalruby"
2
2
 
3
3
  module Adder
4
- crystalize [a: :int, b: :int] => :int
4
+ crystallize [a: :int, b: :int] => :int
5
5
  def add(a, b)
6
6
  a + b
7
7
  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
@@ -1,23 +1,23 @@
1
1
  module CrystalRuby
2
2
  module Adapter
3
- # Use this method to annotate a Ruby method that should be crystalized.
3
+ # Use this method to annotate a Ruby method that should be crystallized.
4
4
  # Compilation and attachment of the method is done lazily.
5
5
  # You can force compilation by calling `CrystalRuby.compile!`
6
- # It's important that all code using crystalized methods is
6
+ # It's important that all code using crystallized methods is
7
7
  # loaded before any manual calls to compile.
8
8
  #
9
9
  # E.g.
10
10
  #
11
- # crystalize [a: :int32, b: :int32] => :int32
12
- # def add(a, b)
11
+ # crystallize :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
+ # crystallize :int32, raw: true
20
+ # def add(a: :int32, b: :int32)
21
21
  # <<~CRYSTAL
22
22
  # a + b
23
23
  # CRYSTAL
@@ -30,104 +30,174 @@ 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
- }
46
- end
47
-
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)
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 crystallize( returns=:void, raw: false, async: false, lib: "crystalruby", &block)
40
+ (self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
41
+ @crystallize_next = {
42
+ raw: raw,
43
+ async: async,
44
+ returns: returns,
45
+ block: block,
46
+ lib: lib
47
+ }
60
48
  end
61
49
  end
62
50
 
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)
70
- end
51
+ # Alias for `crystallize`
52
+ alias :crystalize :crystallize
71
53
 
72
- # We trigger attaching of crystalized instance methods here.
73
- # If a method is added after a crystalize annotation we assume it's the target of the crystalize annotation.
74
- def method_added(method_name)
75
- define_crystalized_method(method_name, instance_method(method_name)) if @crystalize_next
76
- super
54
+ # Exposes a Ruby method to one or more Crystal libraries.
55
+ # Type annotations follow the same rules as the `crystallize` method, but are
56
+ # applied in reverse.
57
+ # @param returns The return type of the method. Optional (defaults to :void).
58
+ # @param [Hash] options The options hash.
59
+ # @option options [Boolean] :raw (false) Pass raw Crystal code to the compiler as a string.
60
+ # @option options [String] :libs (["crystalruby"]) The name of the Crystal librarie(s) to expose the Ruby code to.
61
+ def expose_to_crystal( returns=:void, libs: ["crystalruby"])
62
+ (self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
63
+ @expose_next_to_crystal = {
64
+ returns: returns,
65
+ libs: libs
66
+ }
67
+ end
77
68
  end
78
69
 
79
- # We trigger attaching of crystalized class methods here.
80
- # If a method is added after a crystalize annotation we assume it's the target of the crystalize annotation.
81
- def singleton_method_added(method_name)
82
- define_crystalized_method(method_name, singleton_method(method_name)) if @crystalize_next
83
- super
70
+ # Define a shard dependency
71
+ # This dependency will be automatically injected into the shard.yml file for
72
+ # the given library and installed upon compile if it is not already installed.
73
+ def shard(shard_name, lib: 'crystalruby', **opts)
74
+ CrystalRuby::Library[lib].require_shard(shard_name, **opts)
84
75
  end
85
76
 
86
77
  # Use this method to define inline Crystal code that does not need to be bound to a Ruby method.
87
78
  # This is useful for defining classes, modules, performing set-up tasks etc.
88
- # See: docs for .crystalize to understand the `raw` and `lib` parameters.
79
+ # See: docs for .crystallize to understand the `raw` and `lib` parameters.
89
80
  def crystal(raw: false, lib: "crystalruby", &block)
90
- inline_crystal_body = Template::InlineChunk.render(
81
+ inline_crystal_body = respond_to?(:name) ? Template::InlineChunk.render(
91
82
  {
92
- module_name: name, body: extract_source(block, raw: raw)
93
- }
94
- )
95
- CrystalRuby::Library[lib].crystalize_chunk(
83
+ module_name: name,
84
+ body: SourceReader.extract_source_from_proc(block, raw: raw),
85
+ mod_or_class: self.kind_of?(Class) && self < Types::Type ? "class" : "module",
86
+ superclass: self.kind_of?(Class) && self < Types::Type ? "< #{self.crystal_supertype}" : ""
87
+ }) :
88
+ SourceReader.extract_source_from_proc(block, raw: raw)
89
+
90
+ CrystalRuby::Library[lib].crystallize_chunk(
96
91
  self,
97
92
  Digest::MD5.hexdigest(inline_crystal_body),
98
93
  inline_crystal_body
99
94
  )
100
95
  end
101
96
 
102
- # We attach crystalized class methods here.
97
+
98
+ # This method provides a useful DSL for defining Crystal types in pure Ruby
99
+ # MyType = CRType{ Int32 | Hash(String, Array(Bool) | Float65 | Nil) }
100
+ # @param [Proc] block The block within which we build the type definition.
101
+ def CRType(&block)
102
+ TypeBuilder.build_from_source(block, context: self)
103
+ end
104
+
105
+ private
106
+
107
+ # We trigger attaching of crystallized instance methods here.
108
+ # If a method is added after a crystallize annotation we assume it's the target of the crystallize annotation.
109
+ # @param [Symbol] method_name The name of the method being added.
110
+ def method_added(method_name)
111
+ define_crystallized_method(instance_method(method_name)) if should_crystallize_next?
112
+ expose_ruby_method_to_crystal(instance_method(method_name)) if should_expose_next?
113
+ super
114
+ end
115
+
116
+ # We trigger attaching of crystallized class methods here.
117
+ # If a method is added after a crystallize annotation we assume it's the target of the crystallize annotation.
118
+ # @note This method is called when a method is added to the singleton class of the object.
119
+ # @param [Symbol] method_name The name of the method being added.
120
+ def singleton_method_added(method_name)
121
+ define_crystallized_method(singleton_method(method_name)) if should_crystallize_next?
122
+ expose_ruby_method_to_crystal(singleton_method(method_name)) if should_expose_next?
123
+ super
124
+ end
125
+
126
+ # Helper method to determine if the next method added should be crystallized.
127
+ # @return [Boolean] True if the next method added should be crystallized.
128
+ def should_crystallize_next?
129
+ defined?(@crystallize_next) && @crystallize_next
130
+ end
131
+
132
+ # Helper method to determine if the next method added should be exposed to Crystal libraries.
133
+ # @return [Boolean] True if the next method added should be exposed.
134
+ def should_expose_next?
135
+ defined?(@expose_next_to_crystal) && @expose_next_to_crystal
136
+ end
137
+
138
+ # This is where we extract the Ruby method metadata and invoke the Crystal::Library functionality
139
+ # to compile a stub for the Ruby method into the Crystal library.
140
+ def expose_ruby_method_to_crystal(method)
141
+ returns, libs = @expose_next_to_crystal.values_at(:returns, :libs)
142
+ @expose_next_to_crystal = nil
143
+
144
+ args, source = SourceReader.extract_args_and_source_from_method(method)
145
+ returns = args.delete(:returns) if args[:returns] && returns == :void
146
+ args[:__yield_to] = args.delete(:yield) if args[:yield]
147
+ src = <<~RUBY
148
+ def #{method.name} (#{(args.keys - [:__yield_to]).join(", ")})
149
+ #{source}
150
+ end
151
+ RUBY
152
+
153
+ owner = method.owner.singleton_class? ? method.owner.attached_object : method.owner
154
+ owner.class_eval(src)
155
+ owner.instance_eval(src) unless method.kind_of?(UnboundMethod) && method.owner.ancestors.include?(CrystalRuby::Types::Type)
156
+ method = owner.send(method.kind_of?(UnboundMethod) ? :instance_method : :method, method.name)
157
+
158
+ libs.each do |lib|
159
+ CrystalRuby::Library[lib].expose_method(
160
+ method,
161
+ args,
162
+ returns,
163
+ )
164
+ end
165
+ end
166
+
167
+ # We attach crystallized class methods here.
103
168
  # This function is responsible for
104
169
  # - Generating the Crystal source code
105
170
  # - Overwriting the method and class methods by the same name in the caller.
106
171
  # - Lazily triggering compilation and attachment of the Ruby method to the Crystal code.
107
172
  # - 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}")
173
+ # @param [Symbol] method_name The name of the method being added.
174
+ # @param [UnboundMethod] method The method being added.
175
+ def define_crystallized_method(method)
176
+ CrystalRuby.log_debug("Defining crystallized method #{name}.#{method.name}")
177
+
178
+ returns, block, async, lib, raw = @crystallize_next.values_at(:returns, :block, :async, :lib, :raw)
179
+ @crystallize_next = nil
180
+
181
+ args, source = SourceReader.extract_args_and_source_from_method(method, raw: raw)
110
182
 
111
- args, returns, block, async, lib, raw = @crystalize_next.values_at(:args, :returns, :block, :async, :lib, :raw)
112
- @crystalize_next = nil
183
+ # We can safely claim the `yield` argument name for typing the yielded block
184
+ # because this is an illegal identifier in Crystal anyway.
185
+ args[:__yield_to] = args.delete(:yield) if args[:yield]
113
186
 
114
- CrystalRuby::Library[lib].crystalize_method(
187
+ returns = args.delete(:returns) if args[:returns] && returns == :void
188
+
189
+ CrystalRuby::Library[lib].crystallize_method(
115
190
  method,
116
191
  args,
117
192
  returns,
118
- extract_source(method, raw: raw),
193
+ source,
119
194
  async,
120
195
  &block
121
196
  )
122
197
  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
198
  end
133
199
  end
200
+
201
+ Module.prepend(CrystalRuby::Adapter)
202
+ BasicObject.prepend(CrystalRuby::Adapter)
203
+ 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