Check whether a required file / shard exists on Compilation time?


#1

I was wondering if there’s possibility to sort of repeat this Ruby behavior but in crystal (and at compile time):

begin
  require "some_gem"
  require "./ext_for_some_gem" #< Require my code only if some_gem is available
rescue LoadError => e
  #Do not add my flavored code as the optional gem is not available
end

Which may look in crystal:

{% if require_exists?("some_shard") %}
   require "some_shard"
   require "./ext_for_some_shard"
{% end %}

This will allow also to create more beautiful compiler error code like this:

require "a_shard/ext/kemal"
# ^~~~ ERROR: You required ext/kemal but kemal wasn't found in your 
#             shards.yml ! Please install kemal first by adding:
#             -----
#               kemal:
#                 github: kemalcr/kemal
#             -----
#             in your shards.yml 

I don’t know if anything like this exists already in the macro system. Any thought about it?


#2

It was discussed on Gitter. In 27.0.1 there will be read_file macro method which would raise on file missing – #6967 and #7094. As shards usually being stored in my_project/lib, you could check a shard’s existence like read_file?("lib/my_shard"). However, IIRC @ysbaddaden once mentioned that this would not work if shards aren’t in the lib folder. Thus I’d be happy if we had explicit require? "shard" method.


#3

What’s the usecase? There’s no concept of optional import in most languages, so it doesn’t seem so necessary to me.


#4

What’s the usecase? There’s no concept of optional import in most languages, so it doesn’t seem so necessary to me.

Well I’m not agreeing about it. This concept is quite used in Ruby, but not only in Ruby. In some languages, there’s concept of optional import. Just yesterday, toying with dwm:

     #ifdef XINERAMA
     #include <X11/extensions/Xinerama.h>
     #endif /* XINERAMA */

In Ruby, it’s very common pattern

In cpp it uses flags to add or remove features on build. I’m just thinking this is obsolete with a language like Crystal. Since the shards are present in a relative lib directory, we can assume than if we install a specific shard in our project, it’s for using it.

Example

Let’s assume I’ve a shard to upload in a bucket some files. I offer interface for AWS, Google Cloud Storage, and Digital Ocean.

For each of these gateway I’ve a dependent shard I use to connect to theses API.

Currently:

  • When someone is installing my shard, it will install all the dependent shards, for AWS, Google Cloud etc…
  • The source-code will bloat, while the final user want only one environnement, not all the providers.

OR

  • Explain to the final user how to include the dependency for the environment he wants
  • Ask the final user to require both my shard (require "myshard") and the file connecting to gateway he wants (require "myshard/aws")

Now with this feature, the process for the final user would be:

  • If the user want to use a specific gateway without the shard installed, will drop a compile-time raise error which describe what to do.

#5

I much prefer the require "myshard/extension" because it’s more explicit.


#6

I just want to note that the "myshard/extension" feature has been discussed before in https://github.com/crystal-lang/shards/issues/172

I’m sorry, but we won’t implement this feature. An incompatible solution was chosen long ago, and we don’t plan to change it.


#7

@vladfaust this is about extensions contained within myshard, so that issue isn’t relevant.


#8

I always thought this was a really bad design or pattern in Ruby. Then you end up with a general purpose gem that does X but for some reason there’s if defined?(Rails) inside it.

It’s probably better to have a base shard x, and then another shard x-rails that you would require if using Rails.

That said, this works:

class Foo
end

p {{ @type.constants.any? &.==("Foo") }}

So you could use that… but I don’t recommend it.