jhass changed the topic of #crystal-lang to: The Crystal programming language | https://crystal-lang.org | Crystal 0.35.1 | Fund Crystal's development: https://crystal-lang.org/sponsors | GH: https://github.com/crystal-lang/crystal | Docs: https://crystal-lang.org/docs | Gitter: https://gitter.im/crystal-lang/crystal
<FromGitter> <j8r> You can inherit class methods, so the boilerplate will be minimal (3-5 lines maybe). Plus, you can unit test this classes alone (`from_string` casting the method), without having to define routes and do HTTP calls
deavmi has quit [Ping timeout: 256 seconds]
<FromGitter> <j8r> *casting then test instance methods
deavmi has joined #crystal-lang
<FromGitter> <j8r> I got the point, it will be much more flexible to have object instances
oddp has quit [Ping timeout: 240 seconds]
rocx has joined #crystal-lang
<FromGitter> <j8r> And one can just use vars to avoid having a huge route declaration line
<FromGitter> <j8r> Ok will do, thanks raz
<FromGitter> <j8r> There is a catch: a same instance may be modified by multiple requests, and share a same state :/
<FromGitter> <Blacksmoke16> :sad:
<FromGitter> <j8r> Man, I don't know. I think using class is good for documentation, safer, easier to test. A simple `for` macro can do the trick.
<FromGitter> <Blacksmoke16> whats the problem exactly?
<FromGitter> <j8r> Avoiding boilerplates in query params
<FromGitter> <j8r> As now type Classes are defined
<FromGitter> <Blacksmoke16> cant just call `.new` on the type class
<FromGitter> <Blacksmoke16> ?
<FromGitter> <j8r> But this means no arguments can be passed
<FromGitter> <j8r> Yeah, but as said above, it means a instance is stored in the proc
<FromGitter> <j8r> The query string will be passed to it through an instance method
<FromGitter> <j8r> So, error prone :/
<FromGitter> <Blacksmoke16> double proc?
<FromGitter> <Blacksmoke16> is what i do in athena
<FromGitter> <Blacksmoke16> store a proc that calls another proc that actually instantiates the obj
<FromGitter> <Blacksmoke16> idk if thats an option in this context tho
<FromGitter> <j8r> That's not really the issue here
<FromGitter> <Blacksmoke16> ah ok
<FromGitter> <j8r> Said, the object is `MyQuery.new`. I nees to get a brand new `MyQuery` for each call by using this instance object
<FromGitter> <j8r> I could use `#clone`...
<FromGitter> <Blacksmoke16> id benchmark that, i remember doing some testing with my DI stuff, of clone versus create a whole new container and the latter was faster
<FromGitter> <Blacksmoke16> but if its only like 1 obj prob not a big deal
<FromGitter> <j8r> In athena, query params can only be strings?
<FromGitter> <Blacksmoke16> neg, can be any scalar value like string, number, bool
<FromGitter> <j8r> It depends of the type annotation i guess
<FromGitter> <Blacksmoke16> or use a param converter for more complex types
<FromGitter> <j8r> Not a custom object?
<FromGitter> <j8r> Ha ok
<FromGitter> <j8r> Basically Paramconverter is what is the class
<FromGitter> <Blacksmoke16> the param converter can also access other params, i.e. you could imagine you have `page` and `per_page` query params, and you could use those to provide a `Pagination` object to the action
<FromGitter> <Blacksmoke16> it implements the logic for converting argument(s) into something else
<FromGitter> <j8r> It would like having parametized param converter
<FromGitter> <Blacksmoke16> yea where the class includes all the logic for the conversion
<FromGitter> <j8r> Just seen it, that's possible in the annotation
<FromGitter> <Blacksmoke16> yes
<FromGitter> <j8r> In the example, `by`
<FromGitter> <Blacksmoke16> the 2nd example there
<FromGitter> <Blacksmoke16> yea
<FromGitter> <Blacksmoke16> in your case whats the reasoning for having be a class versus a module?
<FromGitter> <Blacksmoke16> for extra configuration on a per endpoint basis?
<FromGitter> <j8r> You mean a type vs instance?
<FromGitter> <Blacksmoke16> yea, instead of it being a module with a class method on it
<FromGitter> <j8r> Using instances will bring easy extra conf, but unsafety
<FromGitter> <j8r> Using classes is lean, safe, and the schema they represents properly documented
<FromGitter> <Blacksmoke16> not possible to have some before action that does the instantiation?
<FromGitter> <Blacksmoke16> so that a new instance gets provided to the action for each request
<FromGitter> <j8r> Not possible, they are difined as an array :/
<FromGitter> <j8r> Or for path params, as method args
<FromGitter> <Blacksmoke16> If you know the types you can use tuple.from array
<FromGitter> <Blacksmoke16> I.e build an array of args to provide to the action, create a tuple from the array, then splat it to the action method
<FromGitter> <j8r> ... but what's inside won't change
<FromGitter> <Blacksmoke16> Right, it would assume each request has unique instances
<FromGitter> <j8r> The `MyQuery` object instance will still be shared for each request
<FromGitter> <Blacksmoke16> :/ hm, I'm not familiar enough with the implementation to have any ideas
<FromGitter> <j8r> Even if it is a struct, copied by value, if it contains an Array, this one will be shared
<FromGitter> <j8r> Thanks still :)
<FromGitter> <Blacksmoke16> Np, gl :p
<FromGitter> <j8r> or raz, just inside the route block : `my_query.check_length 8, 42`
<FromGitter> <Blacksmoke16> @watzon you still working on that ORM you mentioned a while ago?
rocx has quit [Ping timeout: 258 seconds]
rocx has joined #crystal-lang
postmodern has joined #crystal-lang
rocx has quit [Ping timeout: 260 seconds]
rocx has joined #crystal-lang
f1refly has quit [Ping timeout: 246 seconds]
f1refly has joined #crystal-lang
rocx has quit [Quit: 👏 developers 👏 developers 👏 developers 👏 developers]
<raz> j8r: possibly it could be a namedtuple/hash, keyed with the param name? not `query_parameters: [OptionalExample, RequiredExample]` but: `query_parameters: {"foo": { type: StringParam, min_len: 1, max_len: 3 }, ... }`
<raz> i think that's kinda how smoke does it with the ParamConverter in athena and i've also done a similar thing in my web thingy. not sure if it fits with how gripen is implemented tho
<raz> (esp. with the swagger auto-generation)
<raz> thing is just: when i write an endpoint that takes 5 parameters, atm the declaration of these params would easily be more verbose than the endpoint code itself. i feel like the param declaration should either be a single class ("deserialize to a FooBarObj"), or sth like the above (params can be parametrized). yup something like `my_query.check_length` inside the body could also work, but feels like it would split two things that belong together. hmm
<raz> (also i imagine for the latter you'd need some macro magic to still have these params/constraints show up in swagger. - syntax-wise for the framework-user such a DSL could be very nice indeed)
alexherbo2 has joined #crystal-lang
sorcus has joined #crystal-lang
oddp has joined #crystal-lang
HumanG33k has joined #crystal-lang
alexherbo2 has quit [Ping timeout: 240 seconds]
Human_G33k has quit [Quit: Leaving]
iii has joined #crystal-lang
<FromGitter> <j8r> The problem is the route declaration will become too verbose
<FromGitter> <j8r> It looks ok to have a base query type, shared across several routes if needed, and do route specific logic inside the route
<FromGitter> <j8r> I think your issue is how to reuse exisiting classes for similar params, not the fact of declaring this objects?
<FromGitter> <j8r> At the end, maybe it is more verbose at first, but sometimes more logic are inside this objects, and where route are declared becomes a lot more readable (which has minimal logic)
<FromGitter> <j8r> At the end, that's just multi-line object vs single line named-tuple. Not sure the chars number will be that different
<FromGitter> <j8r> Note also path params are in the same situation
<FromGitter> <j8r> I'm afraid that this kind of syntax you proposed requires magic macros, which i would like to limit
<FromGitter> <j8r> I see route declaration as a compat layer between HTTP and the business logic
<raz> hmm yep. the verbosity is my concern. :) and yes, i also see your point
<raz> the thing is that in the current model i'm bound to essentially duplicate the exact definitions and constraints that i already have in the ORM model (since in most cases the posted values will hit an ORM or other data-layer at some point)
<raz> so either the endpoint-spec should be reflected from the ORM (quickly gets complicated, needs adapters for various ORMs, also has to be flexible cause it's often not a 1:1 thing). or it should be so compact that i don't end up writing more boilerplate for those params than for the endpoint itself (which is what it sadly looks like now :/)
<raz> for a sinatra-style framework (which is where i mentally see gripen) i'm leaning towards the "the DSL should be compact" side
<raz> since i'd use it to "quickly sketch up stuff", where ORM etc. may come in later or never. but if tapping out a few routes takes the same level of verbosity as in a fully integrated framework (amber/lucky/perhaps athena) then the advantage of getting started quicker becomes questionable.
<raz> i think the keyword is "leverage". i basically want to write `get '/foo/:bar', query_params: { batz: { type: String, max_len: 3, min_len: 5 } } do ... end` and have it just work. anything beyond that should be possible but optional.
<raz> but yep, i'll be the first to admit it's a hard a problem. :D
MasterdonX has joined #crystal-lang
Stephie- has joined #crystal-lang
Flipez9 has joined #crystal-lang
commavir_ has joined #crystal-lang
masterdonx2 has quit [*.net *.split]
commavir has quit [*.net *.split]
Stephie has quit [*.net *.split]
Flipez has quit [*.net *.split]
commavir_ is now known as commavir
Flipez9 is now known as Flipez
<raz> just for completeness: instead of a `query_params` argument. some kind of macro/annotation would of course also be perfectly fine.
<FromGitter> <j8r> Note you can also use a symbol, like `query_params: [:some_id]/`
<FromGitter> <j8r> So, there is either very simple/basic, or very powerful with types. There is for now to middle ground, that's what you wish?
<FromGitter> <j8r> Again, nothing stops you to use a macro to generate this types, then use it directly in the route declaration (I think it would be fine for you?)
<FromGitter> <j8r> How I see is: use string/symbol for optional/required queries. Then, eventually, use dedicated types. WDYT?
<FromGitter> <Blacksmoke16> > *<raz>* the thing is that in the current model i'm bound to essentially duplicate the exact definitions and constraints that i already have in the ORM model (since in most cases the posted values will hit an ORM or other data-layer at some point) ⏎ ⏎ im assuming you mean like validation?
<FromGitter> <j8r> raz see the example https://github.com/grip-framework/gripen#block-based, would be as simple as Kemal/Sinatra
<FromGitter> <j8r> I don't see much what's the disavantage of validating inside the route?
<FromGitter> <j8r> (Including in Athena)?
<FromGitter> <Blacksmoke16> for a one off endpoint prob not a problem, but using the same stuff on multiple routes it can get annoying
<FromGitter> <Blacksmoke16> versus having it abstracted into something that you could share
<FromGitter> <j8r> So, you can share the same annotation paramconverter for multiple routes?
<FromGitter> <Blacksmoke16> yea ofc, like that multiple converter we were talking about you could add it to another route with `by: 10` etc
<FromGitter> <Blacksmoke16> multiply*
<FromGitter> <j8r> But you need to redeclare the annotation
lunarkitty has quit [Ping timeout: 260 seconds]
<FromGitter> <Blacksmoke16> correct
<FromGitter> <j8r> That's a line. Put as an annotation, or inside the route with something like `MyObject.new some_str, by: 10` for me is similar
lunarkitty has joined #crystal-lang
<FromGitter> <j8r> A pros I see is maybe the errors could be more consistent using the framework objects
<FromGitter> <j8r> raz your final answer is just use a string/symbol, and validates the string/cast to your object inside the route, and here you go :)
<FromGitter> <j8r> More lines vs one big route declaration line (which btw can be split in multiple ones) is for me similar
<FromGitter> <Blacksmoke16> errors and if you ever want to update it, only have to do it in one place
<raz> j8r: i always found that block based example confusing. `[:required, "optional"]`, huh?
<raz> well, that's in both of them actually
<raz> does that declare two params, one required, one optional? or a required one called option. and what's their type. it's like those guys who blink left and turn right
* raz feels like he'll have to build a micro-athena at some point
<FromGitter> <Blacksmoke16> oh boy :p
<raz> just let me `def foo(bar : Int32)`. and then annotate it with `@Get(route: '/wee', bar: { required: true, min: 0, max: 42 })`
<raz> i think then i can have my peace.
<raz> Blacksmoke16: if you don't do it, someone has to! :P
<FromGitter> <Blacksmoke16> eventually the idea for that would be to allow using validation contracts on query params
<FromGitter> <Blacksmoke16> something like
<FromGitter> <Blacksmoke16> ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f145929775d59377c01b32c]
<raz> Blacksmoke16: yap, i'll take that!
<raz> let me also pass a proc for those more complicated cases
<raz> actually athena already almost does it like that, doesn't it?
<FromGitter> <j8r> raz that's a list of query parameters
* raz scratches head
<FromGitter> <Blacksmoke16> raz: yes, minus the validation constraints, that component is still a WIP
<raz> j8r: why is one of them called required and the other optional? is the first one always required? how do i make the second one also required? :p
<FromGitter> <j8r> And query params at first are always obviously strings
<FromGitter> <j8r> That's just the name of the query, i can change the readme
<FromGitter> <j8r> It was to make it obvious what their property
<FromGitter> <j8r> What's their difference
<FromGitter> <j8r> I could call `:required_id`, `"optional_id"` -– would be better?
<FromGitter> <ImAHopelessDev_gitlab> `upto` and `downto` wow
<FromGitter> <Blacksmoke16> i think the confusion is coming from the faction that apparently symbols are considered to be required, while strings are optional?
<raz> j8r: what makes one required and one optional? in the example they're just two items in the same array
<FromGitter> <Blacksmoke16> the fact*
<FromGitter> <j8r> raz symbol make it required
<raz> j8r: ohhhhhh
<FromGitter> <j8r> string optional, it is more deeply explained in EXAMPLES.md
<FromGitter> <j8r> Oh hum no in fact, should definitely add this 😅
<raz> hm, i think today i'm on athenas side. type-cast based on the `def`. and specify validations in annotations.
<raz> i think python frameworks do it that way
<FromGitter> <j8r> Yeah but the problem is everything is more complex
<raz> not for me, the user
<FromGitter> <j8r> A beginner has to learn a whole nee thing
<raz> for me it's easier :)
<FromGitter> <j8r> The API is much wider in Athena
<raz> well, if done right then the user wouldn't need to learn much
<FromGitter> <j8r> So maybe you prefer more symfony than sinatra then a the end ;)
<raz> yes but that's not because of this feature. it's because blacksmoke can't ever stop abstracting until he has a dependency injected control inversed param conversion framework that could drive a nucular reactor
<FromGitter> <j8r> Lol haha
<raz> well, if one could stop him early, like right after those 3 lines he just pasted...
<raz> cause those are really all i want from my super-sinatra :D
<FromGitter> <Blacksmoke16> one thing im not sold on is how to handle the violations
<raz> raise exception and wrap them into a nice user-overridable ValidationErrorHandler
<FromGitter> <j8r> raz what's the downside of using the casting isnide the route?
<raz> (that returns json-api format by default)
<FromGitter> <Blacksmoke16> at work the violations are provided as an argument to the action
<FromGitter> <Blacksmoke16> then we do ⏎ ⏎ ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f145bb064b6213da149e4ff]
<FromGitter> <ImAHopelessDev_gitlab> is that php
<FromGitter> <ImAHopelessDev_gitlab> triggered
<raz> j8r: well, since the `def` already says what type it is, it would be ideal to not have to duplicate it
<FromGitter> <Blacksmoke16> id like to still support that i think, maybe have default be raise exception
<FromGitter> <j8r> raz there will be no duplication
<FromGitter> <j8r> Just call `#validate_things` or, `NewType.new params[:my_id]`
<FromGitter> <j8r> That a line inside the route block, vs a new annotation line on top of it
<FromGitter> <j8r> (In case of Athena)
<raz> j8r: hmm, can't really follow that now, would have to see a sample
<FromGitter> <j8r> Inside the route block, you have acces to params with `params`
<FromGitter> <j8r> Then, you can ger the value of one an validate/cast it
<FromGitter> <j8r> *and
<raz> that's kinda what i want to write (hope it makes sense)
<raz> i think athena is fairly close. i think gripen also, just from a different direction
<FromGitter> <j8r> Inside the method, you could use `bar.validate min: 0, max: 10`
<raz> yup, i'd take that as well. but not sure how that will play with swagger auto-generation?
<FromGitter> <j8r> Or with sinatra, kemal, gripen, use the validation/cast inside
* raz is all about the number of keystrokes, but doesn't care so much in which order they need to take place :D
<FromGitter> <Blacksmoke16> fwiw `MyWidgetSerializer` wouldnt be needed as you could serializer `MyWidget` directly :P
<raz> Blacksmoke16: yup, if it's already json serializable (or some other way) that can ofc also be used. don't really care about how it's done in detail, just that custom types should also be handled :)
sorcus has quit [Ping timeout: 256 seconds]
<FromGitter> <Blacksmoke16> in this context you would need a param converter
sorcus has joined #crystal-lang
<raz> that's also fine. as long i don't have to specify it explicitly. cause those tend to be So::Long::And::Repetitive
<raz> just have a name convention, yet more keystrokes saved :)
<FromGitter> <Blacksmoke16> you would, something like `@[ART::ParamConverter("article", converter: RequestBody, model: MyWidget)]`
<raz> well, guess that's still tolerable
<FromGitter> <Blacksmoke16> idk how you would do it automatically
<raz> where's the interesting part tho (parameters for the converter)
<raz> Blacksmoke16: automatically = type is called "Foo". so look for "FooConverter"
<raz> because in practice it's always called "FooConverter" anyway. so can just as well save the user from having to state the obvious every time ;)
<FromGitter> <Blacksmoke16> id argue in that case unless `FooConverter` is doing something super specific to `Foo`, you'd be better off with a more generic converter
<FromGitter> <Blacksmoke16> i.e. hence `RequestBody` converter instead of one for each model
<raz> well, that's getting into model vs param converter now
<FromGitter> <Blacksmoke16> not really, the model is an argument to the param converter that tells it what it would be deserializing
<raz> yap, so i have an endpoint that takes two strings, an int, and a MyWidget
<raz> for strings / int i'm probably happy with the builtin converters, just need to be able to tell them a max-len/min-len etc.
<FromGitter> <Blacksmoke16> same logic can be reused regardless of what specific model should be converted, assuming ofc they all are serializable
<raz> for MyWidget i probably want a MyWidgetConverter, and _probably_ almost always the same
<FromGitter> <Blacksmoke16> yea you'll need to wait for the validation stuff for that
<FromGitter> <Blacksmoke16> not really, within the request converter you could check if the model object is validatable and run the validations
<FromGitter> <Blacksmoke16> i.e. like
<FromGitter> <Blacksmoke16> ```class MyWidget ⏎ @[Assert::Range(0..50)] ⏎ property title : String ⏎ end``` [https://gitter.im/crystal-lang/crystal?at=5f145fc6fd3832489b1bd8e1]
<FromGitter> <Blacksmoke16> which the request body converter would run validations after deserialization
<raz> Blacksmoke16: it also has to work with classes that are not athena models. or does athena make these annotations available in any class?
<FromGitter> <Blacksmoke16> they would be useable in anything that would do like `include AVD::Validatable`
<raz> well, that's the part i don't want. ;) the difference between micro-framework that can be sprinkled in vs "building an athena app"
<FromGitter> <Blacksmoke16> hm?
<FromGitter> <j8r> I guess, you can't have best of both worlds raz :/
<raz> j8r: well, it's possible. it's the difference between having a Converter for my classes. or having the framework assume that my custom class is a Model that has framework stuff included
<FromGitter> <Blacksmoke16> i mean to be clear, all this is optional and/or configurable.
<FromGitter> <Blacksmoke16> are free to define your own converters and stuff that do whatever you want
<FromGitter> <Blacksmoke16> i.e. `if model.is_a? AVD::Validatable` run validations
<FromGitter> <Blacksmoke16> im not sure i see the problem
<raz> yup, i'll have to try it out at some point. i may very well be missing parts that do or don't work
<FromGitter> <Blacksmoke16> 👍
<FromGitter> <Blacksmoke16> and again to be clear the idea is more like "if i want to validate things i can just include this module and add some annotations and it'll just work", if you dont need that just done do either of those and it'll still work
<raz> hm yup. that sounds good. i think a zero-to-hero tutorial is needed at some point. "i start with `def my_endpoint(foo : Int32, bar : String)`". "then i add `@[ART::Get("/wee")` and it's served under /wee, taking an Int32 and a String param (throwing cast error if non-int is passed)". "now i add `@[ART::Validate foo, min=1, max=3]` to have the int validated." "now i change the 2nd param to `bar : MyWidget`" ...
<raz> (very roughly speaking, syntax errors included :D)
<FromGitter> <Blacksmoke16> i did add a getting started section, is a good start i think
<raz> yes, it's all looking good 👍 - i need to read up
<raz> also the api-docs are a joy, clicked around them some and it all makes sense
<FromGitter> <Blacksmoke16> ❤️ i try :p
<FromGitter> <Blacksmoke16> namespacing ftw 😉 ha
<raz> yup, it works :)
livcd has joined #crystal-lang
andremedeiros has quit [Read error: Connection reset by peer]
andremedeiros has joined #crystal-lang
andremed- has joined #crystal-lang
andremedeiros has quit [Ping timeout: 264 seconds]
andremed- is now known as andremedeiros
postmodern has quit [Quit: Leaving]
<FromGitter> <RespiteSage> Is there a good way of deleting characters at specific indices in a string (that is, returning a new string resulting from that deletion)?
<FromGitter> <RespiteSage> Right now it seems like I'll need to operate on `str.chars`.
<FromGitter> <j8r> you could build a new string, and use `each_char`
<FromGitter> <j8r> @Blacksmoke16 https://ucf.github.io/Athena-Framework/
<FromGitter> <RespiteSage> Yeah, that would probably be more efficient.
<FromGitter> <RespiteSage> Though I'd have to manually keep track of the indices...
<FromGitter> <j8r> `each_char_with_index` ;)
<FromGitter> <RespiteSage> Lol. I don't know how I didn't see that.
<FromGitter> <j8r> But if you have lots of deletion it will be inefficient
<FromGitter> <RespiteSage> Yeah. I'm stripping license headers out of files, so hopefully I would only have to delete once, maybe twice.
<FromGitter> <j8r> I think it may be a good idea to have a `String#select(&)`
<FromGitter> <j8r> lchop won't do?
<FromGitter> <RespiteSage> So I'm using a `Regex::MatchData` to give me the indices for deletion.
<FromGitter> <RespiteSage> I want to make sure that I get anything that's actually in the middle of the file.
<FromGitter> <j8r> Why you don't use `String#sub`?
<FromGitter> <j8r> This will replace what's matching the regex with what's inside the block, or second arg
<FromGitter> <RespiteSage> Yeah, I guess that makes much more sense.
<FromGitter> <RespiteSage> I'll probably `gsub`, though.
<FromGitter> <j8r> 👍
<FromGitter> <naqvis> @RespiteSage ⏎ ⏎ ```code paste, see link``` [https://gitter.im/crystal-lang/crystal?at=5f148b935ed1d537746e3623]
<FromGitter> <RespiteSage> Is `String#delete_at` in a release yet?
<FromGitter> <naqvis> I made that up
<FromGitter> <naqvis> did the monkey patching
<FromGitter> <RespiteSage> Ah, gotcha.
<FromGitter> <naqvis> PS, there isn't any checks or index validation
<FromGitter> <naqvis> but just a thought that slices can be used to cut down the string
<FromGitter> <naqvis> aah, just went through history and saw the PR link shared above, and that's more awsome. So better wait for PR to be merged
<FromGitter> <Blacksmoke16> @j8r :0 they took my name :p
Vexatos has quit [Quit: ZNC Quit]
Vexatos has joined #crystal-lang
rocx has joined #crystal-lang
coderobe has quit [Quit: Ping timeout (120 seconds)]
Flipez has quit [Quit: Ping timeout (120 seconds)]
early has quit [Quit: Leaving]
Flipez has joined #crystal-lang
coderobe has joined #crystal-lang
early has joined #crystal-lang
<FromGitter> <j8r> Haha, I have gone back to 2017 in their history. Can even be older
<FromGitter> <j8r> You could rename to Dyonisos Framework haha
<FromGitter> <Blacksmoke16> or they could 😉
<FromGitter> <j8r> I can create an issue in their repo
<FromGitter> <j8r> (Joking) :)
<FromGitter> <Blacksmoke16> ha, i bet that'd go over well
Human_G33k has joined #crystal-lang
HumanG33k has quit [Ping timeout: 265 seconds]
<FromGitter> <asterite> @RespiteSage it will be in 1.0, I just merged it :-) https://github.com/crystal-lang/crystal/pull/9398
<FromGitter> <RespiteSage> Excellent. :)
<FromGitter> <asterite> Note that I think the monkey-patch provided by @naqvis doesn't work well if the string has unicode chars
iii has quit [Ping timeout: 260 seconds]
oddp has quit [Quit: quit]