faster_path 0.3.9 → 0.3.10
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.
- checksums.yaml +4 -4
- data/Cargo.lock +0 -7
- data/Cargo.toml +0 -1
- data/Gemfile +1 -0
- data/README.md +16 -7
- data/faster_path.gemspec +1 -1
- data/lib/faster_path.rb +6 -1
- data/lib/faster_path/version.rb +1 -1
- data/src/pathname.rs +11 -50
- data/src/pathname_sys.rs +1 -15
- data/src/plus.rs +54 -56
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32af4c3fa17e05003792d14b3e8a34ce26fd206395f345fc58a13c8cc63b37b7
|
4
|
+
data.tar.gz: 7ec503bf0957f176fa5daa1d4910a67af9c3b06bb391e573586fcaadc1157a88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff3f4a219cb46f561eec9a2138d1282773ad2ceddcd0664202c2396202efbb1ecb2b8839347dd65bcf595faf93b6d6d6b85d6a912c18380b8ff10ccd878379ab
|
7
|
+
data.tar.gz: 27e3886927a1479b5d28e1b2a5d96cb5bb86c240b87664010b1be9d181afebf456476a43c1e7843f890b2af581ccd398dca91c3dd6f1021380e156402f2d90ab
|
data/Cargo.lock
CHANGED
@@ -1,13 +1,7 @@
|
|
1
|
-
[[package]]
|
2
|
-
name = "array_tool"
|
3
|
-
version = "1.0.3"
|
4
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
5
|
-
|
6
1
|
[[package]]
|
7
2
|
name = "faster_path"
|
8
3
|
version = "0.0.1"
|
9
4
|
dependencies = [
|
10
|
-
"array_tool 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
11
5
|
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
12
6
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
13
7
|
"ruby-sys 0.3.0 (git+https://github.com/danielpclark/ruby-sys?branch=playground)",
|
@@ -55,7 +49,6 @@ dependencies = [
|
|
55
49
|
]
|
56
50
|
|
57
51
|
[metadata]
|
58
|
-
"checksum array_tool 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8f8cb5d814eb646a863c4f24978cff2880c4be96ad8cde2c0f0678732902e271"
|
59
52
|
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
|
60
53
|
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
61
54
|
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
|
data/Cargo.toml
CHANGED
@@ -18,6 +18,5 @@ crate-type = ["dylib"]
|
|
18
18
|
[dependencies]
|
19
19
|
ruby-sys = { git = "https://github.com/danielpclark/ruby-sys", branch = "playground" }
|
20
20
|
ruru = { git = "https://github.com/danielpclark/ruru", branch = "playground" }
|
21
|
-
array_tool = "1.0"
|
22
21
|
lazy_static = "1.0"
|
23
22
|
memchr = "2.0.1"
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,12 +3,11 @@
|
|
3
3
|
[](https://travis-ci.org/danielpclark/faster_path)
|
4
4
|
[](https://ci.appveyor.com/project/danielpclark/faster-path/branch/master)
|
5
5
|
[](https://github.com/danielpclark/faster_path/tags)
|
6
|
-
[](https://github.com/danielpclark/faster_path/pulse)
|
7
7
|
[](https://github.com/danielpclark/faster_path/releases)
|
8
8
|
[](https://coveralls.io/github/danielpclark/faster_path?branch=master)
|
9
9
|
[](http://inch-ci.org/github/danielpclark/faster_path)
|
10
10
|
[](https://www.codetriage.com/danielpclark/faster_path)
|
11
|
-
[](https://twitter.com/share?url=https%3A%2F%2Fgithub.com%2Fdanielpclark%2Ffaster_path&via=6ftdan&hashtags=Ruby&text=You%20could%20save%2015%25%20or%20more%20on%20website%20page%20load%20time%20by%20switching%20to%20the%20FasterPath%20gem.)
|
12
11
|
|
13
12
|
#### This gem shaves off more than 30% of my Rails application page load time.
|
14
13
|
|
@@ -94,6 +93,18 @@ I've said this about Sprockets but this required two other gems to be updated as
|
|
94
93
|
|sass 3.2.19|sass 5.0.4|
|
95
94
|
|bootstrap-sass 3.3.4.1|bootstrap-sass 3.3.6|
|
96
95
|
|
96
|
+
## Performance Specifics
|
97
|
+
|
98
|
+
The headline for the amount for improvement on this library is specific to only the improvement made with the method `chop_basename`. Just so you know; in my initial release I had a bug in which that method immediately returned nothing. Now the good thing about this is that it gave me some very valuable information. First I found that all my Rails site tests still passed. Second I found that all my assets no longer loaded in the website. And third, and most importantly, I found my Rails web pages loaded just more than 66% faster without the cost of time that `chop_basename` took.
|
99
|
+
|
100
|
+
**That's right; the path handling for assets in your website \*consumes more than 2/3rds of your websites page load time.**
|
101
|
+
|
102
|
+
So now we have some real numbers to work with We can be generoues and use 66% as our margin of area to improve over _(for `chop_basename` specifically, not counting the benefit from improving the performance in other file path related methods)_. That means we want to remove as much of that percentage from the overall systems page load time. The original headline boasts over 33% performance improvement — that was when `chop_basename` was improved by just over 50%. Now `chop_basename` is improved by 83.4%. That alone should make your site run 55.044% faster now _(given your performance profile stats are similar to mine)_.
|
103
|
+
|
104
|
+
## What Rails Versions Will This Apply To?
|
105
|
+
|
106
|
+
As mentioned earlier Sprockets, which handles assets, changed away from using `Pathname` at all when moving from major version 2 to 3. So if you're using Sprockets 3 or later you won't reap the biggest performance reqards from using this gem for now _(it's my goal to have this project become a core feature that Rails depends on and yes… that's a big ask)_. That is unless you write you're own implementation to re-integrate the use of `Pathname` and `FasterPath` into your asset handling library. For now just know that the Sprockets 2 series primarily works with Rails 4.1 and earlier. It may work in later Rails versions but I have not investigated this.
|
107
|
+
|
97
108
|
## Status
|
98
109
|
|
99
110
|
* Rust compilation is working
|
@@ -115,7 +126,7 @@ curl -sSf https://static.rust-lang.org/rustup.sh | sh
|
|
115
126
|
Add this line to your application's Gemfile:
|
116
127
|
|
117
128
|
```ruby
|
118
|
-
gem 'faster_path', '~> 0.3.
|
129
|
+
gem 'faster_path', '~> 0.3.10'
|
119
130
|
```
|
120
131
|
|
121
132
|
And then execute:
|
@@ -153,8 +164,8 @@ Current methods implemented:
|
|
153
164
|
| `FasterPath.entries` | `Pathname#entries` | 41.0% |
|
154
165
|
| `FasterPath.extname` | `File.extname` | 63.1% |
|
155
166
|
| `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 88.9% |
|
156
|
-
| `FasterPath.plus` | `Pathname#join` |
|
157
|
-
| `FasterPath.plus` | `Pathname#plus` |
|
167
|
+
| `FasterPath.plus` | `Pathname#join` | 79.1% |
|
168
|
+
| `FasterPath.plus` | `Pathname#plus` | 94.7% |
|
158
169
|
| `FasterPath.relative?` | `Pathname#relative?` | 92.6% |
|
159
170
|
| `FasterPath.relative_path_from` | `Pathname#relative_path_from` | 93.3% |
|
160
171
|
|
@@ -205,8 +216,6 @@ Whenever feasible implement it in Rust.
|
|
205
216
|
After checking out the repo, make sure you have Rust installed, then run `bundle`.
|
206
217
|
Run `rake test` to run the tests, and `rake bench` for benchmarks.
|
207
218
|
|
208
|
-
Learn and share performance tips on the [wiki](https://github.com/danielpclark/faster_path/wiki)!
|
209
|
-
|
210
219
|
### Building and running tests
|
211
220
|
|
212
221
|
First, bundle the gem's development dependencies by running `bundle`. Rust compilation is included in the current rake commands.
|
data/faster_path.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.require_paths = ['lib']
|
26
26
|
|
27
27
|
spec.add_dependency 'bundler', '~> 1.12'
|
28
|
-
spec.add_dependency 'rake', '~> 12.
|
28
|
+
spec.add_dependency 'rake', '~> 12.3'
|
29
29
|
spec.add_dependency 'thermite', '0.13.0'
|
30
30
|
spec.add_development_dependency 'read_source', '~> 0.2.6'
|
31
31
|
spec.add_development_dependency 'minitest', '~> 5.10'
|
data/lib/faster_path.rb
CHANGED
@@ -6,8 +6,13 @@ require 'fiddle'
|
|
6
6
|
require 'fiddle/import'
|
7
7
|
|
8
8
|
# FasterPath module behaves as a singleton object with the alternative method
|
9
|
-
# implementations for Pathname
|
9
|
+
# implementations for `Pathname`, and some for `File`, available directly on it.
|
10
10
|
#
|
11
|
+
# New projects are recommend to reference methods defined directly on `FasterPath`.
|
12
|
+
# Existing websites may use the `FasterPath.sledgehammer_everything!` method to
|
13
|
+
# directly injet the more performant implementations of path handling in to their
|
14
|
+
# existing code ecosystem. To do so you will need to
|
15
|
+
# `require 'faster_path/optional/monkeypatches'` beforehand.
|
11
16
|
module FasterPath
|
12
17
|
FFI_LIBRARY = begin
|
13
18
|
toplevel_dir = File.dirname(__dir__)
|
data/lib/faster_path/version.rb
CHANGED
data/src/pathname.rs
CHANGED
@@ -9,7 +9,6 @@ use plus;
|
|
9
9
|
use relative_path_from;
|
10
10
|
use debug;
|
11
11
|
use helpers::{TryFrom, to_str};
|
12
|
-
use pathname_sys::null_byte_check;
|
13
12
|
use path_parsing::{SEP, find_last_non_sep_pos};
|
14
13
|
|
15
14
|
use ruru;
|
@@ -23,10 +22,11 @@ use ruru::{
|
|
23
22
|
Class,
|
24
23
|
VerifiedObject,
|
25
24
|
Exception as Exc,
|
26
|
-
AnyException as Exception
|
25
|
+
AnyException as Exception,
|
27
26
|
};
|
28
27
|
use ruru::types::{Value, ValueType};
|
29
|
-
use std::
|
28
|
+
use std::borrow::Cow;
|
29
|
+
use std::path::{MAIN_SEPARATOR, Path};
|
30
30
|
use std::fs;
|
31
31
|
|
32
32
|
type MaybeArray = Result<ruru::Array, ruru::result::Error>;
|
@@ -45,31 +45,6 @@ impl Pathname {
|
|
45
45
|
Pathname { value: instance.value() }
|
46
46
|
}
|
47
47
|
|
48
|
-
pub fn new_checked(path: AnyObject) -> Result<Pathname, Exception> {
|
49
|
-
let pth: Value = if Class::from_existing("String").case_equals(&path) {
|
50
|
-
path.value()
|
51
|
-
} else if path.respond_to("to_path") {
|
52
|
-
path.send("to_path", None).value()
|
53
|
-
} else {
|
54
|
-
return Err(
|
55
|
-
Exception::new(
|
56
|
-
"ArgumentError",
|
57
|
-
Some("The type for the argument provided to Pathname.new was invalid.")
|
58
|
-
)
|
59
|
-
)
|
60
|
-
};
|
61
|
-
|
62
|
-
if null_byte_check(path.value()) {
|
63
|
-
return Err( Exception::new("ArgumentError", Some("pathname contains null byte")) )
|
64
|
-
}
|
65
|
-
|
66
|
-
// if it crashes then dup the path string here before assigning to @path
|
67
|
-
let mut instance = Class::from_existing("Pathname").allocate();
|
68
|
-
instance.instance_variable_set("@path", RString::from(pth).to_any_object());
|
69
|
-
|
70
|
-
Ok(Pathname { value: instance.value() })
|
71
|
-
}
|
72
|
-
|
73
48
|
pub fn to_any_object(&self) -> AnyObject {
|
74
49
|
AnyObject::from(self.value())
|
75
50
|
}
|
@@ -77,7 +52,7 @@ impl Pathname {
|
|
77
52
|
|
78
53
|
impl From<Value> for Pathname {
|
79
54
|
fn from(value: Value) -> Self {
|
80
|
-
Pathname { value
|
55
|
+
Pathname { value }
|
81
56
|
}
|
82
57
|
}
|
83
58
|
|
@@ -302,29 +277,15 @@ pub fn pn_has_trailing_separator(pth: MaybeString) -> Boolean {
|
|
302
277
|
}
|
303
278
|
|
304
279
|
pub fn pn_join(args: MaybeArray) -> AnyObject {
|
305
|
-
let
|
306
|
-
let
|
307
|
-
let mut
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
let mut result = String::new();
|
313
|
-
|
314
|
-
loop {
|
315
|
-
if qty == 0 { break; }
|
316
|
-
|
317
|
-
let item = args.pop();
|
318
|
-
result = plus::plus_paths(&anyobject_to_string(item).unwrap(), &result);
|
319
|
-
if result.as_bytes().get(0) == Some(&SEP) {
|
320
|
-
return Pathname::new(&result).to_any_object()
|
280
|
+
let paths = args.unwrap().into_iter().map(|arg| anyobject_to_string(arg).unwrap()).collect::<Vec<_>>();
|
281
|
+
let mut paths_iter = paths.iter().rev();
|
282
|
+
let mut result = Cow::Borrowed(paths_iter.next().unwrap().as_str());
|
283
|
+
for part in paths_iter {
|
284
|
+
result = plus::plus_paths(&part, result.as_ref());
|
285
|
+
if result.as_bytes().first() == Some(&SEP) {
|
286
|
+
break;
|
321
287
|
}
|
322
|
-
|
323
|
-
qty -= 1;
|
324
288
|
}
|
325
|
-
|
326
|
-
let result = plus::plus_paths(&path_self, &result);
|
327
|
-
|
328
289
|
Pathname::new(&result).to_any_object()
|
329
290
|
}
|
330
291
|
|
data/src/pathname_sys.rs
CHANGED
@@ -2,23 +2,9 @@ use ruru::{AnyObject, Array, Object, AnyException};
|
|
2
2
|
use ruru::types::{Argc, Value, CallbackPtr};
|
3
3
|
use ruru::util::str_to_cstring;
|
4
4
|
extern crate ruby_sys;
|
5
|
-
use self::ruby_sys::{class, util, vm
|
5
|
+
use self::ruby_sys::{class, util, vm};
|
6
6
|
use ::pathname;
|
7
7
|
extern crate memchr;
|
8
|
-
use self::memchr::memchr;
|
9
|
-
use std::slice;
|
10
|
-
|
11
|
-
pub fn null_byte_check(value: Value) -> bool {
|
12
|
-
unsafe {
|
13
|
-
let str = string::rb_string_value_ptr(&value) as *const u8;
|
14
|
-
|
15
|
-
// `rb_str_len` is a ruby_sys specific thing. Consider:
|
16
|
-
// extern { fn rb_str_strlen(value: Value) -> c_long } as isize
|
17
|
-
let len = string::rb_str_len(value) as usize;
|
18
|
-
|
19
|
-
memchr(b'\0', slice::from_raw_parts(str, len)).is_some()
|
20
|
-
}
|
21
|
-
}
|
22
8
|
|
23
9
|
pub fn raise(exception: AnyException) {
|
24
10
|
unsafe { vm::rb_exc_raise(exception.value()); }
|
data/src/plus.rs
CHANGED
@@ -1,83 +1,80 @@
|
|
1
|
-
|
2
|
-
use std::path::Path;
|
1
|
+
use std::borrow::Cow;
|
3
2
|
use std::str;
|
3
|
+
use std::path::MAIN_SEPARATOR;
|
4
|
+
|
4
5
|
use chop_basename::chop_basename;
|
5
|
-
use
|
6
|
-
use dirname::dirname;
|
7
|
-
use self::array_tool::vec::Shift;
|
8
|
-
use std::ops::Index;
|
6
|
+
use path_parsing::SEP;
|
9
7
|
|
10
|
-
pub fn plus_paths(path1: &str, path2: &str) ->
|
11
|
-
let mut prefix2 = path2
|
8
|
+
pub fn plus_paths<'a>(path1: &'a str, path2: &str) -> Cow<'a, str> {
|
9
|
+
let mut prefix2 = path2;
|
12
10
|
let mut index_list2: Vec<usize> = vec![];
|
13
|
-
let mut basename_list2: Vec
|
14
|
-
|
11
|
+
let mut basename_list2: Vec<&str> = vec![];
|
15
12
|
loop {
|
16
|
-
match chop_basename(
|
17
|
-
None => { break }
|
13
|
+
match chop_basename(prefix2) {
|
14
|
+
None => { break; }
|
18
15
|
Some((pfx2, basename2)) => {
|
19
|
-
prefix2 = pfx2
|
20
|
-
index_list2.
|
21
|
-
basename_list2.
|
22
|
-
}
|
16
|
+
prefix2 = pfx2;
|
17
|
+
index_list2.push(pfx2.len());
|
18
|
+
basename_list2.push(basename2);
|
19
|
+
}
|
23
20
|
}
|
24
21
|
}
|
25
22
|
if !prefix2.is_empty() {
|
26
|
-
return path2.to_string()
|
23
|
+
return path2.to_string().into();
|
27
24
|
};
|
28
25
|
|
29
|
-
let
|
30
|
-
|
26
|
+
let result_prefix: Cow<str>;
|
27
|
+
let mut prefix1 = path1;
|
31
28
|
loop {
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
let mut new_len = basename_list2.len() - count_trailing(".", &basename_list2);
|
30
|
+
index_list2.truncate(new_len);
|
31
|
+
basename_list2.truncate(new_len);
|
32
|
+
match chop_basename(prefix1) {
|
33
|
+
None => {
|
34
|
+
result_prefix = prefix1.into();
|
35
|
+
break;
|
36
|
+
}
|
38
37
|
Some((pfx1, basename1)) => {
|
39
|
-
prefix1 = pfx1
|
40
|
-
if basename1 == "." { continue };
|
41
|
-
if basename1 == ".." || basename_list2.
|
42
|
-
prefix1.
|
43
|
-
break
|
44
|
-
}
|
38
|
+
prefix1 = pfx1;
|
39
|
+
if basename1 == "." { continue; };
|
40
|
+
if basename1 == ".." || basename_list2.last() != Some(&"..") {
|
41
|
+
result_prefix = [prefix1, basename1].concat().into();
|
42
|
+
break;
|
45
43
|
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
if new_len > 0 {
|
47
|
+
new_len -= 1;
|
48
|
+
index_list2.truncate(new_len);
|
49
|
+
basename_list2.truncate(new_len);
|
46
50
|
}
|
47
|
-
index_list2.shift();
|
48
|
-
basename_list2.shift();
|
49
51
|
}
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
if !r1 {
|
56
|
-
r1 = basename(&prefix1[..], "").contains("/");
|
57
|
-
if r1 {
|
58
|
-
while !basename_list2.is_empty() && basename_list2.first().unwrap() == ".." {
|
59
|
-
index_list2.shift();
|
60
|
-
basename_list2.shift();
|
61
|
-
}
|
62
|
-
}
|
53
|
+
if !result_prefix.is_empty() && result_prefix.as_bytes().iter().cloned().all(|b| b == SEP) {
|
54
|
+
let new_len = basename_list2.len() - count_trailing("..", &basename_list2);
|
55
|
+
index_list2.truncate(new_len);
|
56
|
+
basename_list2.truncate(new_len);
|
63
57
|
}
|
64
|
-
if
|
65
|
-
let
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
58
|
+
if let Some(last_index2) = index_list2.last() {
|
59
|
+
let suffix = &path2[*last_index2..];
|
60
|
+
match (result_prefix.as_bytes().last(), suffix.as_bytes().first()) {
|
61
|
+
(Some(&SEP), Some(&SEP)) => [&result_prefix, &suffix[1..]].concat().into(),
|
62
|
+
(Some(&SEP), Some(_)) | (Some(_), Some(&SEP)) => [&result_prefix, suffix].concat().into(),
|
63
|
+
(None, Some(_)) => suffix.to_string().into(),
|
64
|
+
_ => format!("{}{}{}", result_prefix.as_ref(), MAIN_SEPARATOR, suffix).into(),
|
71
65
|
}
|
72
66
|
} else {
|
73
|
-
if
|
74
|
-
|
67
|
+
if result_prefix.is_empty() {
|
68
|
+
".".into()
|
75
69
|
} else {
|
76
|
-
|
70
|
+
result_prefix
|
77
71
|
}
|
78
72
|
}
|
73
|
+
}
|
79
74
|
|
80
|
-
|
75
|
+
#[inline(always)]
|
76
|
+
fn count_trailing(x: &str, xs: &Vec<&str>) -> usize {
|
77
|
+
xs.iter().rev().take_while(|&c| c == &x).count()
|
81
78
|
}
|
82
79
|
|
83
80
|
#[test]
|
@@ -90,6 +87,7 @@ fn it_will_plus_same_as_ruby() {
|
|
90
87
|
assert_eq!("/b" , plus_paths("a" , "/b"));
|
91
88
|
|
92
89
|
assert_eq!("/" , plus_paths("/" , ".."));
|
90
|
+
assert_eq!("////" , plus_paths("////", ""));
|
93
91
|
assert_eq!("." , plus_paths("a" , ".."));
|
94
92
|
assert_eq!("a" , plus_paths("a/b", ".."));
|
95
93
|
assert_eq!("../.." , plus_paths(".." , ".."));
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faster_path
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel P. Clark
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-03-
|
11
|
+
date: 2018-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '12.
|
33
|
+
version: '12.3'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '12.
|
40
|
+
version: '12.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: thermite
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|