cloud_powers 0.2.7.1 → 0.2.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f93298a530fa939a15a4e6631b7cda3bc64e558d
4
- data.tar.gz: 6f0498a0f808e3aa52cfefe4a58878a6393ae929
3
+ metadata.gz: 2903624136e4fac9b36e82f2374ba9f9dc640661
4
+ data.tar.gz: 6baf7a5cbcdff6352d626e8fd709025f8fd246fc
5
5
  SHA512:
6
- metadata.gz: 1516b53957f5229ef3ea64ac37f639262060a0337dbcdbae46ff584c5f03f23018cc284097b34f7813186d5fa0f3cbd76b0a5d1100662eeacc65532789e340dc
7
- data.tar.gz: 0ab5d2c4213506152e93c21cbcc76153a10c967df3283531615e43898898f8ec8f66816c4be115675856fe67ec2f78f98cf4fb454b02046677c280d570d2bf6b
6
+ metadata.gz: 63905c6b21fee990c6e5d92c89c603866f5d4bee8ec545615c9172d3a684a462ca969fb0fb0b0318bfd50eff3723f6fe49a8992427aec00a4f3c9ac5273a2c61
7
+ data.tar.gz: 75788dace7b21879318c8b0a666bba446823cc3616b135a9b29bc7fb98d90dd355708743a72e37b08eb09e4e5d7215227a031c002952c68dc0c5e0bd73e648be
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cloud_powers (0.2.7.1)
4
+ cloud_powers (0.2.7.2)
5
5
  activesupport-core-ext (~> 4)
6
6
  aws-sdk (~> 2)
7
7
  dotenv (~> 2.1)
@@ -13,12 +13,16 @@ module Smash
13
13
  # resources in the account that zfind searches for, using the <tt>ACCOUNT_NUMBER</tt>
14
14
  # key.
15
15
  #
16
- # === Returns
16
+ # Returns
17
17
  # <tt>Aws::Credentials</tt>
18
18
  #
19
- # === Example
19
+ # Example
20
20
  # Auth.creds
21
- # => Aws::Credentials
21
+ # => Aws::Credentials # can be used to authenticate to AWS
22
+ #
23
+ # Notes
24
+ # * This method relies on +#zfind()+ to locate the key/secret strings
25
+ # * See +Smash::CloudPowers::Zenv#zfind()+
22
26
  def self.creds
23
27
  @creds ||= Aws::Credentials.new(
24
28
  zfind(:aws_access_key_id),
@@ -29,10 +33,10 @@ module Smash
29
33
  # This method is able to be called before an object is instantiated in order
30
34
  # to provide a region in AWS-landia.
31
35
  #
32
- # === Returns
36
+ # Returns
33
37
  # The region set in configuration or a <tt>'us-west-2'</tt> default <tt>String</tt>
34
38
  #
35
- # === Example
39
+ # Example
36
40
  # Auth.region
37
41
  # => 'us-east-1'
38
42
  def self.region
@@ -11,7 +11,7 @@ module Smash
11
11
 
12
12
  # Get the region from the environment/context or use a default region for AWS API calls.
13
13
  #
14
- # === Returns
14
+ # Returns
15
15
  # +String+
16
16
  def region
17
17
  zfind(:aws_region) || 'us-west-2'
@@ -25,10 +25,10 @@ module Smash
25
25
  # * * region - defaulted to use the <tt>#region()</tt> method
26
26
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
27
27
  #
28
- # === Returns
29
- # <tt>AWS::EC2::Client</tt>
28
+ # Returns
29
+ # +AWS::EC2::Client+
30
30
  #
31
- # === Sample Usage
31
+ # Example
32
32
  # images = ec2.describe_images
33
33
  # images.first[:image_id]
34
34
  # # => 'asdf'
@@ -53,7 +53,7 @@ module Smash
53
53
  # * * region: defaulted to use the `#region()` method
54
54
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
55
55
  #
56
- # === Returns
56
+ # Returns
57
57
  # Aws::EC2::Image
58
58
  def image(name, opts = {})
59
59
  config = {
@@ -65,16 +65,17 @@ module Smash
65
65
  end
66
66
 
67
67
  # Get or create an Kinesis client and cache that client so that a Context is more well tied together
68
+ #
68
69
  # Parameters
69
70
  # * opts <tt>Hash</tt>
70
71
  # * * stub_responses: defaulted to false but it can be overriden with the desired responses for local testing
71
72
  # * * region: defaulted to use the `#region()` method
72
73
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
73
74
  #
74
- # === Returns
75
- # AWS::Kinesis client
75
+ # Returns
76
+ # +AWS::Kinesis client+
76
77
  #
77
- # === Example
78
+ # Example
78
79
  # pipe_to('somePipe') { update_body(status: 'waHoo') } # uses Aws::Kinesis::Client.put_recor()
79
80
  # # => sequence_number: '1676151970'
80
81
  def kinesis(opts = {})
@@ -96,12 +97,12 @@ module Smash
96
97
  # * * region: defaulted to use the `#region()` method
97
98
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
98
99
  #
99
- # === Returns
100
- # AWS::S3 client
100
+ # Returns
101
+ # +AWS::S3 client+
101
102
  #
102
- # === Example
103
- # expect(s3.head_bucket).to be_empty
104
- # # passing expectation
103
+ # Example
104
+ # expect(s3.head_bucket('exampleBucket')).to be_empty
105
+ # # passing test
105
106
  def s3(opts = {})
106
107
  config = {
107
108
  stub_responses: false,
@@ -120,10 +121,10 @@ module Smash
120
121
  # * * region: defaulted to use the `#region()` method
121
122
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
122
123
  #
123
- # === Returns
124
- # AWS::SNS client
124
+ # Returns
125
+ # +AWS::SNS client+
125
126
  #
126
- # === Example
127
+ # Example
127
128
  # create_channel!('testBroadcast') # uses Aws::SNS::Client
128
129
  # # => true
129
130
  def sns(opts = {})
@@ -145,10 +146,10 @@ module Smash
145
146
  # * * region: defaulted to use the `#region()` method
146
147
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
147
148
  #
148
- # === Returns
149
- # AWS::SQS client
149
+ # Returns
150
+ # +AWS::SQS client+
150
151
  #
151
- # === Example
152
+ # Example
152
153
  # create_queue('someQueue') # Uses Aws::SQS::Client
153
154
  def sqs(opts = {})
154
155
  config = {
@@ -96,7 +96,7 @@ module Smash
96
96
  # Parameters
97
97
  # * <tt>key</tt> - +String+ (optional)
98
98
  #
99
- # === Returns
99
+ # Returns
100
100
  # * +Array+ if key is blank
101
101
  # * +String+ if key is given
102
102
  def metadata_request(key = '')
@@ -6,6 +6,38 @@ module Smash
6
6
  module Storage
7
7
  include Smash::CloudPowers::AwsResources
8
8
 
9
+ # Searches a local task storage location for the given +file+ name
10
+ # if it exists - exit the method
11
+ # if it does <i>not</i> exist - get the file from s3 and place it in
12
+ # the directory that was just searched bucket using +#zfind()+
13
+ #
14
+ # Parameters
15
+ # * file +String+ - the name of the file we're searching for
16
+ #
17
+ # Returns
18
+ # nothing
19
+ #
20
+ # Example
21
+ # * file tree
22
+ # project_root- |
23
+ # |_sub_directory
24
+ # | |_current_file.rb
25
+ # |_task_storage
26
+ # | |_demorific.rb
27
+ # | |_foobar.rb
28
+ # * code:
29
+ # source_task('demorific')
30
+ # # file tree doesn't change because this file exists
31
+ # source_task('custom_greetings')
32
+ # # file tree now looks like this
33
+ # * new file tree
34
+ # project_root- |
35
+ # |_sub_directory
36
+ # | |_current_file.rb
37
+ # |_task_storage
38
+ # | |_demorific.rb
39
+ # | |_foobar.rb
40
+ # | |_custom_greetings.js # could be an after effects JS script
9
41
  def source_task(file)
10
42
  # TODO: better path management
11
43
  bucket = zfind('task storage')
@@ -19,17 +51,33 @@ module Smash
19
51
  end
20
52
  end
21
53
 
54
+ # Search through a bucket to find a file, based on a regex
55
+ #
56
+ # Parameters
57
+ # * bucket +String+ - the bucket to search through in AWS
58
+ # * pattern +Regex+ - the Regex pattern you want to use for the
59
+ # search
60
+ #
61
+ # Example
62
+ # matches = search('neuronTasks', /[Dd]emo\w*/)
63
+ # # => ['Aws::S3::Type::ListObjectOutPut','Aws::S3::Type::ListObjectOutPut',...] # anything that matched that regex
64
+ # matches.first.contents.size
65
+ # # => 238934 # integer representation of the file size
22
66
  def search(bucket, pattern)
23
67
  s3.list_objects(bucket: bucket).contents.select do |o|
24
68
  o.key =~ pattern
25
69
  end
26
70
  end
27
71
 
72
+ # Send the log files to the S3 log file bucket
73
+ #
74
+ # Returns
75
+ # +Aws::S3::Type::PutObjectOutput+
28
76
  def send_logs_to_s3
29
77
  File.open(log_file) do |file|
30
78
  s3.put_object(
31
79
  bucket: log_bucket,
32
- key: @instance_id,
80
+ key: instance_id,
33
81
  body: file
34
82
  )
35
83
  end
@@ -29,6 +29,7 @@ module Smash
29
29
  #################
30
30
 
31
31
  # Creates a connection point for 1..N nodes to create a connection with the Broadcast
32
+ # <b>Not Implimented</b>
32
33
  #
33
34
  # Parameters
34
35
  # * channel +String+
@@ -60,6 +61,7 @@ module Smash
60
61
  end
61
62
 
62
63
  # Creates a connection to the Broadcast so that new messages will be picked up
64
+ #
63
65
  # Parameters channel <Broadcast::Channel>
64
66
  def listen_on(channel)
65
67
  sns.subscribe(
@@ -70,6 +72,7 @@ module Smash
70
72
  end
71
73
 
72
74
  # Lists the created topics in SNS.
75
+ #
73
76
  # Returns results <Array
74
77
  def real_channels
75
78
  results = []
@@ -84,9 +87,13 @@ module Smash
84
87
  end
85
88
 
86
89
  # Send a message to a Channel using SNS#publish
87
- # Parameters [opts <Hash>]:
88
- # this includes all the keys AWS uses but for now it only has defaults
90
+ #
91
+ # Parameters
92
+ # * opts +Hash+ - this includes all the keys AWS uses but for now it only has defaults
89
93
  # for topic_arn and the message
94
+ # * * +:topic_arn+ - the ARN for the topic in AWS
95
+ # * * +:message+ - the message that should be broadcasted to whoever is listening on this
96
+ # Channel (AWS Topic)
90
97
  def send_broadcast(opts = {})
91
98
  msg = opts.delete(:message) || ""
92
99
 
@@ -38,6 +38,7 @@ module Smash
38
38
  end
39
39
 
40
40
  # Use the KCL and LangDaemon to read from a stream
41
+ #
41
42
  # Parameters stream String
42
43
  #
43
44
  # Notes
@@ -70,6 +71,7 @@ module Smash
70
71
  end
71
72
 
72
73
  # Read messages from the Pipe without using the KCL
74
+ #
73
75
  # Parameters stream String
74
76
  #
75
77
  # Notes
@@ -10,7 +10,14 @@ module Smash
10
10
  include Smash::CloudPowers::Helper
11
11
  include Smash::CloudPowers::Zenv
12
12
 
13
- attr_accessor :address, :name, :poller, :sqs
13
+ # The URL the Aws::SQS::Queue uses
14
+ attr_accessor :address
15
+ # The name the Aws::SQS::Queue uses
16
+ attr_accessor :name
17
+ # An Aws::SQS::QueuePoller for this Board/SQS::Queue
18
+ attr_accessor :poller
19
+ # An Aws::SQS::Client. See +#Smash::CloudPowers::AwsResources.sqs()+
20
+ attr_accessor :sqs
14
21
 
15
22
  # Creates a Board object.
16
23
  # The +#new()+ method is wrapped in +#build()+ and +#create!()+ but isn't private so
@@ -22,7 +29,7 @@ module Smash
22
29
  #
23
30
  # Returns
24
31
  # +Queue::Board+
25
- def initialize(name, this_sqs)# = sqs)
32
+ def initialize(name, this_sqs = sqs)
26
33
  @sqs = this_sqs
27
34
  @name = name
28
35
  end
@@ -11,13 +11,24 @@ module Smash
11
11
  # A simple Struct that acts as a Name to URL map
12
12
  #
13
13
  # Parameters
14
- # * <tt>:set_name</tt> - name of the Queue +String+
15
- # * <tt>:set_url</tt> - the url for the Queue +String+
14
+ # * :set_name +String+ (optional) - An optional name. It should be the same name as the
15
+ # the Board and/or Queue you're working with, or else this Struct isn't that useful
16
+ # * :set_url +String+ (optional) - An optional URL. It should be the same URL as the
17
+ # the Board and/or Queue you're working with, or else this Struct isn't that useful
18
+ #
19
+ # Attributes
20
+ # * name +String+ - the +:set_name+ or parse the +#address()+ for the name
21
+ # * url +String+ - the +:set_url+ or add the name to the end of a best guess at the URL
16
22
  #
17
23
  # Example
18
- # name_url_map = NUMap.new(nil, 'https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
19
- # name_url_map.name
20
- # # => 'fooBar'
24
+ # name_url_map = NUMap.new(nil, 'https://sqs.us-west-53.amazonaws.com/001101010010/fooBar')
25
+ # name_url_map.name
26
+ # # => 'fooBar'
27
+ #
28
+ # # and now in reverse
29
+ # url_name_map = NUMap.new('snargleBargle')
30
+ # url_name_map.address
31
+ # # => 'https://sqs.us-west-53.amazonaws.com/001101010010/snargleBargle'
21
32
  NUMap = Struct.new(:set_name, :set_url) do
22
33
  # Gives you back the name, even if it hasn't been set
23
34
  #
@@ -1,3 +1,3 @@
1
1
  module CloudPowers
2
- VERSION = "0.2.7.1"
2
+ VERSION = "0.2.7.2"
3
3
  end
@@ -1,17 +1,25 @@
1
1
  require 'dotenv'
2
2
  require_relative 'helper'
3
+
3
4
  module Smash
4
5
  module CloudPowers
5
6
  # This module provides some environment variable management and functionality
7
+ # Hopefully it should provide us with some "Zen", when dealing with normally
8
+ # annoying env issues. Meh, it probably won't but I like the name, so it stays :}
6
9
  # System ENV, dotenv ENV and instance variables are considered for now but this
7
- # will also use elasticache/redis...some other stuff too, in the coming weeks
10
+ # will also use elasticache/redis...some other stuff too, in the coming versions
8
11
  module Zenv
9
12
  include Smash::CloudPowers::Helper
10
13
 
11
14
  # Attempts to find a file by searching the current directory for the file
12
- # then walking up the file tree and searching at each stop
13
- # Parameters name <String>: name of the file or directory to find
14
- # Returns Pathname: path to the file or directory given as the `name` param
15
+ # then walking up the file tree and searching at each stop all the way up
16
+ # to the root directory
17
+ #
18
+ # Parameters
19
+ # * name +String+ - name of the file or directory to find
20
+ #
21
+ # Returns
22
+ # +Pathname+ - path to the file or directory given as the +name+ parameter
15
23
  def file_tree_search(name)
16
24
  next_dir = Pathname.new(`pwd`.strip).parent
17
25
  current_dir = Pathname.new(`pwd`.strip)
@@ -24,8 +32,12 @@ module Smash
24
32
  return nil
25
33
  end
26
34
 
27
- # Search through the .env variables for a key or if no key is given,
28
- # return all the .env-vars and their values
35
+ # Search through the {Dotenv}[https://github.com/bkeepers/dotenv]
36
+ # variables for a key or if no key is given, return all the .env-vars
37
+ # and their values
38
+ #
39
+ # Parameters
40
+ # * key +String+ -
29
41
  def env_vars(key = '')
30
42
  return ENV if key.empty?
31
43
  ENV[to_snake(key).upcase]
@@ -33,8 +45,11 @@ module Smash
33
45
 
34
46
  # Search through the instance variables for a key or if no key is given,
35
47
  # return all the i-vars and their values
48
+ #
36
49
  # Parameters [key <String]: The key to search for
50
+ #
37
51
  # Returns
52
+ # the value of the +key+ searched for
38
53
  def i_vars(key = '')
39
54
  if key.empty?
40
55
  return self.instance_variables.inject({}) do |r,v|
@@ -47,9 +62,23 @@ module Smash
47
62
  # PROJECT_ROOT should be set as early as possible in this Node's initilize
48
63
  # method. This method tries to search for it, using #zfind() and if a `nil`
49
64
  # result is returned from that search, `pwd` is used as the PROJECT_ROOT.
50
- # Returns Path to the project root or where ever `pwd` resolves to <Pathname>
51
- # TODO: improve this...it needs to find the gem's method's caller's project
65
+ #
66
+ # Returns
67
+ # +Pathname+ - path to the project root or where ever <tt>`pwd`</tt> resolves
68
+ # to for the caller
69
+ #
70
+ # Notes
71
+ # * TODO: improve this...it needs to find the gem's method's caller's project
52
72
  # root or at least the gem's method's caller's file's location.
73
+ #
74
+ # Example
75
+ # # called from cerebrum/cerebrum.rb in /home/ubuntu
76
+ # project_root
77
+ # # => '/home/ubuntu/cerebrum/'
78
+ # # or
79
+ # # called from go_nuts.rb#begin_going_crazy():(line -999999999.9) in /Users/crazyman/.ssh/why/all/the/madness/
80
+ # project_root
81
+ # # => '/Users/crazyman/.ssh/why/all/the/madness/'
53
82
  def project_root
54
83
  if @project_root.nil?
55
84
  file_home = Pathname.new(
@@ -60,18 +89,31 @@ module Smash
60
89
  @project_root
61
90
  end
62
91
 
63
- # Manually set the `@project_root` i-var as a `Pathname` object.
64
- # Parameters New path to the project root <String|Pathname>
65
- # Returns @project_root <Pathname>
92
+ # Manually set the +@project_root+ i-var as a +Pathname+ object.
93
+ #
94
+ # Parameters New path to the project root +String+|+Pathname+
95
+ #
96
+ # Returns +Pathname+ - +@project_root+
97
+ #
98
+ # Example
99
+ # project_root
100
+ # # => '/home/ubuntu/cerebrum/'
101
+ # project_root = Pathname.new(`pwd`)
102
+ # project_root == `pwd`
103
+ # # => true
66
104
  def project_root=(var)
67
105
  @project_root = Pathname.new(var)
68
106
  end
69
107
 
70
108
  # Search through the system environment variables for a key or if no key
71
109
  # is given, return all the system-env-vars and their values
110
+ #
72
111
  # Parameters [key <String>]: The key to search for
73
- # Returns Value <String> for the given key or if no key was given, a
74
- # Hash with { key => value, ... } is returned for all keys with a value.
112
+ #
113
+ # Returns
114
+ # * if a +key+ is given as a parameter, +String+
115
+ # * if no +key+ is given as a parameter, +Hash+
116
+ # with this structure +{ key => value, ... }+ is returned for all keys with a value.
75
117
  # Keys with no value are ommitted from the result.
76
118
  def system_vars(key = '')
77
119
  if key.empty?
@@ -92,15 +134,21 @@ module Smash
92
134
  end
93
135
 
94
136
  # ZFind looks for the key in a preditermined order of importance:
95
- # * i-vars are considered first becuase they might be tracking different
96
- # locations for multiple tasks or something like that.
97
- # * dotenv files are second because they were manually set, so for sure
98
- # it's important
99
- # * System Env[@] variables are up next. Hopefully by this time we've found
100
- # our information but if not, it should "search" through the system env too.
101
- # Parameters key <String>: The key to search for
102
- # Returns <String>
103
- # TODO: implement a search for all 3 that can find close matches
137
+ # * i-vars are considered first becuase they might be tracking different
138
+ # locations for multiple tasks or something like that.
139
+ # * dotenv files are second because they were manually set, so for sure
140
+ # it's important
141
+ # * System Env[@] variables are up next. Hopefully by this time we've found
142
+ # our information but if not, it should "search" through the system env too.
143
+ #
144
+ # Parameters
145
+ # * key +String+|+Symbol+ - the key to search for
146
+ #
147
+ # Returns
148
+ # +String+
149
+ #
150
+ # Notes
151
+ # * TODO: implement a search for all 3 that can find close matches
104
152
  def zfind(key)
105
153
  project_root if @project_root.nil?
106
154
  res = (i_vars[to_snake(key).upcase] or
@@ -35,9 +35,10 @@ module Smash
35
35
  # * * region - defaulted to use the <tt>#region()</tt> method
36
36
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
37
37
  #
38
- # === Returns
38
+ # Returns
39
39
  # <tt>AWS::EC2::Client</tt>
40
- # === Sample Usage
40
+ #
41
+ # Example
41
42
  # config = stub_responses: {
42
43
  # run_instances: {
43
44
  # instances: [{ instance_id: 'asd-1234', launch_time: Time.now, state: { name: 'running' }]
@@ -91,16 +92,17 @@ module Smash
91
92
  end
92
93
 
93
94
  # Stub data for a SNS client
95
+ #
94
96
  # Parameters
95
97
  # * opts +Hash+
96
98
  # * * stub_responses: defaulted to false but it can be overriden with the desired responses for local testing
97
99
  # * * region: defaulted to use the `#region()` method
98
100
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
99
101
  #
100
- # === Returns
102
+ # Returns
101
103
  # AWS::SNS client
102
104
  #
103
- # === Example
105
+ # Example
104
106
  # config = {
105
107
  # stub_responses: {
106
108
  # create_topic: {},
@@ -136,10 +138,10 @@ module Smash
136
138
  # * * region: defaulted to use the `#region()` method
137
139
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
138
140
  #
139
- # === Returns
141
+ # Returns
140
142
  # AWS::Kinesis client
141
143
  #
142
- # === Example
144
+ # Example
143
145
  # config = {
144
146
  # stub_responses: {
145
147
  # create_stream: {},
@@ -205,10 +207,10 @@ module Smash
205
207
  # * * region: defaulted to use the `#region()` method
206
208
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
207
209
  #
208
- # === Returns
210
+ # Returns
209
211
  # AWS::S3 client
210
212
  #
211
- # === Example
213
+ # Example
212
214
  # config = {
213
215
  # stub_responses: {
214
216
  # head_bucket: {}
@@ -234,10 +236,10 @@ module Smash
234
236
  # * * region: defaulted to use the `#region()` method
235
237
  # * * AWS::Credentials object, which will also scour the context and environment for your keys
236
238
  #
237
- # === Returns
239
+ # Returns
238
240
  # AWS::SQS client
239
241
  #
240
- # === Example
242
+ # Example
241
243
  # create_queue('someQueue') # uses AWS::SQS
242
244
  # some_queue_url = queue_search('someQueue').first # uses AWS::SQS
243
245
  def self.queue_stub(opts = {})
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud_powers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7.1
4
+ version: 0.2.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Phillipps
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2016-10-12 00:00:00.000000000 Z
12
+ date: 2016-10-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport-core-ext