crystalruby 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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