gizmo-codescratch 0.0.2

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.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Craig Wickesser
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ gizmo [![Build Status](https://secure.travis-ci.org/codescratch/gizmo.png?branch=master)](http://travis-ci.org/codescratch/gizmo)
2
+ =====
3
+
4
+ Removing the crud from developing an API to support CRUD, in the name of Gizmo.
5
+
6
+ ## Installation
7
+
8
+ The preferred method for installing Gizmo is with bundler. Simply add Gizmo to your `Gemfile`:
9
+
10
+ gem "gizmo"
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ class PostsController < ApplicationController
16
+ include Gizmo
17
+
18
+ # for a simple HTML request
19
+ def index
20
+ gizmo_response = find_all Post
21
+ @posts = gizmo_response.data
22
+ end
23
+
24
+ # for a JSON request
25
+ def index
26
+ render find_all(Post.order_by([[:updated_at, :desc]])).to_hash
27
+ end
28
+
29
+ def show
30
+ render find(Post, params[:id]).to_hash
31
+ end
32
+
33
+ def create
34
+ render make(Post, params[:post]).to_hash
35
+ end
36
+
37
+ def update
38
+ render modify(CmsSupport::Post, params[:id], params[:post]).to_hash
39
+ end
40
+
41
+ def destroy
42
+ render delete(CmsSupport::Post, params[:id]).to_hash
43
+ end
44
+ end
45
+ ```
46
+
47
+ ## TODO
48
+
49
+ * look for TODO's
50
+ * abtract persistence layer to suport Mongoid, ActiveRecord, etc
51
+ * error handling :)
52
+ * The gizmo more or less expects to be included into a Rails controller.
data/lib/gizmo.rb ADDED
@@ -0,0 +1,100 @@
1
+ require 'logger'
2
+ require 'mongoid'
3
+
4
+ require "gizmo/version"
5
+ require "gizmo/models"
6
+ require "gizmo/base_operation"
7
+ require "gizmo/find_all"
8
+ require "gizmo/find"
9
+ require "gizmo/search"
10
+ require "gizmo/make"
11
+ require "gizmo/modify"
12
+ require "gizmo/delete"
13
+ require "gizmo/aop"
14
+
15
+ module Gizmo
16
+ @logger = Logger.new STDERR
17
+
18
+ class << self
19
+ attr_accessor :logger
20
+ end
21
+
22
+ # Find all items
23
+ #
24
+ # @param [Mongoid::Criteria] criteria
25
+ #
26
+ # @return [Gizmo::Response]
27
+ def find_all(*args)
28
+ context = create_context
29
+ operation = FindAll.new(context)
30
+ response = operation.call(*args)
31
+ operation.set_response_headers response
32
+ response
33
+ end
34
+
35
+ # Find an item
36
+ #
37
+ # @param [Mongoid::Criteria] criteria
38
+ # @param [String, Moped::BSON::ObjecId] id
39
+ #
40
+ # @return [Gizmo::Response]
41
+ def find(*args)
42
+ context = create_context
43
+ Find.new(context).call(*args)
44
+ end
45
+
46
+ def search(*args)
47
+ context = create_context
48
+ Search.new(context).call(*args)
49
+ end
50
+
51
+ # Make a new instance of an item
52
+ #
53
+ # @param [Mongoid::Criteria] criteria
54
+ # @param [Hash] attrs
55
+ #
56
+ # @return [Gizmo::Response]
57
+ def make(*args)
58
+ context = create_context
59
+ operation = Make.new(context)
60
+ response = operation.call(*args)
61
+ operation.set_response_headers response
62
+ response
63
+ end
64
+
65
+ # Modify an item
66
+ #
67
+ # @param [Mongoid::Criteria] criteria
68
+ # @param [String, Moped::BSON::ObjecId] id
69
+ # @param [Hash] attrs
70
+ #
71
+ # @return [Gizmo::Response]
72
+ def modify(*args)
73
+ context = create_context
74
+ Modify.new(context).call(*args)
75
+ end
76
+
77
+ # Delete an item
78
+ #
79
+ # @param [Mongoid::Criteria] criteria
80
+ # @param [String, Moped::BSON::ObjecId] id
81
+ #
82
+ # @return [Gizmo::Response]
83
+ def delete(*args)
84
+ context = create_context
85
+ Delete.new(context).call(*args)
86
+ end
87
+
88
+ private
89
+
90
+ def create_context
91
+ if defined?(ActionController) && self.kind_of?(ActionController::Base)
92
+ Context.new(gizmo: self, format: request.format.to_sym, callback: params[:callback])
93
+ else
94
+ Context.new(gizmo: self, format: :json, callback: nil)
95
+ end
96
+ end
97
+
98
+
99
+ end
100
+
data/lib/gizmo/aop.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Gizmo
2
+ require 'gizmo/aop/exception_handling'
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'aquarium'
2
+
3
+ module Gizmo
4
+ class ExceptionHandling
5
+ include Aquarium::DSL
6
+
7
+ def self.log_error(error)
8
+ puts "ERROR:: #{error.message}: #{error.backtrace}"
9
+
10
+ if defined?(Rails) && Rails.logger
11
+ Rails.logger.error "#{error.message}: #{error.backtrace}"
12
+ end
13
+ end
14
+
15
+ #around :calls_to => [:find_all, :find, :make, :modify, :delete], :in_types => /Controller$/ do |jp, obj, *args|
16
+ types = [Gizmo::FindAll, Gizmo::Find, Gizmo::Make, Gizmo::Modify, Gizmo::Delete]
17
+ around :calls_to => [:call], :in_types => types do |jp, operation, *args|
18
+ production = ENV['RAILS_ENV'] == 'production'
19
+
20
+ begin
21
+ response = jp.proceed
22
+ rescue Mongoid::Errors::DocumentNotFound => ex
23
+ err = Error.new("Unable to locate #{args.first} with id=#{args[1]}", production ? [] : ex.backtrace.slice(0, 10))
24
+ log_error err
25
+ response = operation.create_response
26
+ response.status = 404
27
+ response.data = err
28
+ rescue Mongoid::Errors::Validations => ex
29
+ err = ValidationError.new("Validation failed for one or more fields", ex.document.errors)
30
+ log_error err
31
+ response = operation.create_response
32
+ response.status = 422
33
+ response.data = err
34
+ rescue Exception => ex
35
+ err = Error.new("[test] Internal server error occurred: #{ex.message}", production ? [] : ex.backtrace.slice(0, 10))
36
+ log_error err
37
+ response = operation.create_response
38
+ response.status = 500
39
+ response.data = err
40
+ end
41
+ response
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module Gizmo
2
+ class BaseOperation
3
+
4
+ attr_reader :context
5
+
6
+ # Creates a new Find and sets its context
7
+ #
8
+ # @param [Gizmo::Context] context
9
+ def initialize(context)
10
+ @context = context
11
+ end
12
+
13
+ def create_response
14
+ Response.new(format: context.format, callback: context.callback, status: default_status)
15
+ end
16
+
17
+ def default_status
18
+ 200
19
+ end
20
+
21
+ # Set headers on the response
22
+ #
23
+ # @param [Gizmo::Response] gizmo_response a response used to set headers on the HTTP response
24
+ def set_response_headers(gizmo_response)
25
+ if context.gizmo.respond_to?(:headers) && gizmo_response.headers
26
+ gizmo_response.headers.each do |name, value|
27
+ context.gizmo.headers[name] = value
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ module Gizmo
2
+ class Delete < BaseOperation
3
+
4
+ # Delete a single item using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for deleting a single item
7
+ # @param [String, Moped::BSON::ObjectId] id the ID of the item being deleted
8
+ #
9
+ # @return [Gizmo::Response]
10
+ def call(criteria, id)
11
+ response = create_response
12
+ criteria.find(id).delete
13
+ response.data = {}
14
+ response
15
+ end
16
+ end
17
+ end
data/lib/gizmo/find.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Gizmo
2
+ class Find < BaseOperation
3
+
4
+ # Find a single item using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for finding a single item
7
+ # @param [String, Moped::BSON::ObjectId] id the ID of the item being found
8
+ #
9
+ # @return [Gizmo::Response]
10
+ def call(criteria, id=nil)
11
+ response = create_response
12
+ if criteria.is_a? Mongoid::Criteria
13
+ response.data = criteria.where(:id=>id).first
14
+ elsif !criteria.respond_to?(:find) && id.nil?
15
+ response.data = criteria
16
+ else
17
+ response.data = criteria.find id
18
+ end
19
+ raise Mongoid::Errors::DocumentNotFound.new(criteria.klass, :id=>id) if response.data.nil?
20
+ response
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Gizmo
2
+ class FindAll < BaseOperation
3
+
4
+ # Find all items using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for finding all items
7
+ #
8
+ # @return [Gizmo::Response]
9
+ def call(criteria)
10
+ response = create_response
11
+ all_items = criteria.all
12
+ response.header 'X-Total-Results', all_items.count
13
+ response.data = all_items
14
+ response
15
+ end
16
+ end
17
+ end
data/lib/gizmo/make.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Gizmo
2
+ class Make < BaseOperation
3
+
4
+
5
+ # Make a single item using the provided criteria.
6
+ #
7
+ # @param [Mongoid::Criteria] criteria the criteria to use for making a single item
8
+ # @param [Hash] attrs the attributes used to make a new instance
9
+ # @param [Proc] location_proc a Proc that will be invoked with the ID of the newly
10
+ # created item, defaults to nil.
11
+ #
12
+ # @return [Gizmo::Response]
13
+ def call(criteria, attrs, location_proc=nil)
14
+
15
+ response = create_response
16
+ item = criteria.create!(attrs)
17
+ response.data = item
18
+
19
+ if context.gizmo.respond_to?(:url_for)
20
+ if location_proc.nil?
21
+ url = context.gizmo.url_for item
22
+ else
23
+ url = location_proc.call(item.id)
24
+ end
25
+
26
+ response.header :Location, url unless url.nil?
27
+ end
28
+ response
29
+ end
30
+
31
+ def default_status
32
+ 201
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,6 @@
1
+ module Gizmo
2
+ require 'gizmo/models/response'
3
+ require 'gizmo/models/context'
4
+ require 'gizmo/models/error'
5
+ require 'gizmo/models/validation_error'
6
+ end
@@ -0,0 +1,15 @@
1
+ module Gizmo
2
+ # Provides context when performing an operation (i.e. find_all, find)
3
+ class Context
4
+
5
+ attr_accessor :format, :callback, :gizmo
6
+
7
+ # TODO document options
8
+ def initialize(attrs={})
9
+ attrs.each do |key, val|
10
+ setter = :"#{key}="
11
+ send setter, val if respond_to?(setter)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Gizmo
2
+ class Error
3
+ attr_reader :message, :backtrace
4
+
5
+ def initialize(message, backtrace=nil)
6
+ @message = message
7
+ @backtrace = backtrace
8
+ end
9
+
10
+ def as_json(opts={})
11
+ {message: message, backtrace: backtrace}
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ module Gizmo
2
+ class Response
3
+
4
+ attr_accessor :format, :data, :callback, :status, :headers
5
+
6
+ # TODO document options
7
+ def initialize(attrs={})
8
+ @headers = {}
9
+
10
+ attrs.each do |name, val|
11
+ setter = :"#{name}="
12
+ send(setter, val) if respond_to? setter
13
+ end
14
+ end
15
+
16
+ def header(name, value)
17
+ headers[name.to_s] = value.to_s
18
+ end
19
+
20
+ def to_hash(opts={})
21
+ hash = {
22
+ format => data.as_json(opts),
23
+ status: status,
24
+ callback: callback
25
+ }
26
+ hash[:headers] = headers unless headers.nil? || headers.empty?
27
+ hash
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module Gizmo
2
+ class ValidationError < Error
3
+ attr_reader :errors
4
+
5
+ def initialize(message, errors, backtrace=nil)
6
+ @message = message
7
+ @errors = errors
8
+ @backtrace = backtrace
9
+ end
10
+
11
+ def as_json(opts={})
12
+ {message: message, errors: errors, backtrace: backtrace}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ module Gizmo
2
+ class Modify < BaseOperation
3
+
4
+ # Modify a single item using the provided criteria.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for finding the item to update
7
+ # @param [String, Moped::BSON::ObjectId] id the ID of the item being updated
8
+ # @param [Hash] attrs the attributes used to update an existing instance
9
+ # @param [Proc] update_strategy The default update strategy
10
+ # does a mass update of attributes, which is not always ideal. A block can
11
+ # be given which will be passed two parameters: the item to update and the attributes.
12
+ # The block must return the data it wishes to send back in the response.
13
+ #
14
+ # @return [Gizmo::Response]
15
+ def call(criteria, id, attrs, update_strategy=nil)
16
+ attrs.delete '_id'
17
+
18
+ response = create_response
19
+ #TODO: refactor this pattern
20
+ if criteria.is_a? Mongoid::Criteria
21
+ item = criteria.where(:id=>id).first
22
+ raise Mongoid::Errors::DocumentNotFound.new(criteria.klass, :id=>id) if item.nil?
23
+ else
24
+ item = criteria.find id
25
+ end
26
+
27
+ if update_strategy.nil?
28
+ item.update_attributes!(attrs)
29
+ else
30
+ item = update_strategy.call item, attrs
31
+ end
32
+ response.data = item
33
+ response
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Gizmo
2
+ class Search < BaseOperation
3
+
4
+ # Search for items.
5
+ #
6
+ # @param [Mongoid::Criteria] criteria the criteria to use for searching the database
7
+ # @param [Hash] params parameters used for searching
8
+ # @option params [String] :term the field to search
9
+ # @option params [String] :query the value used for searching
10
+ # @option params [Integer] :num_items the maximum number of items to return, default 10
11
+ # @option params [Boolean] :fuzzy true if fuzzy matching should be used, false otherwise, default to false
12
+ # @param [Proc] if a block is given, it will be passed the criteria and params. It should
13
+ # return the result/data. note: the block will be used to perform the search.
14
+ #
15
+ # @return [Gizmo::Response]
16
+ def call(criteria, params={})
17
+ response = create_response
18
+ if block_given?
19
+ response.data = yield criteria, params
20
+ else
21
+ term = params[:term]
22
+ query = params[:query]
23
+ num_items = (params[:num_items] || 10).to_i
24
+ num_items = 10 if num_items > 100 || num_items < 1
25
+
26
+ fuzzy = !!((params[:fuzzy] || "") =~ /true/i)
27
+ if fuzzy
28
+ query = /#{query}/i
29
+ end
30
+
31
+ response.data = criteria.where(term => query).limit(num_items)
32
+ end
33
+ response
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Gizmo
2
+ VERSION = "0.0.2" unless defined?(Gizmo::VERSION)
3
+ end
@@ -0,0 +1,12 @@
1
+ class FutzController
2
+ include Gizmo
3
+ attr_accessor :headers
4
+
5
+ def initialize
6
+ @headers = {}
7
+ end
8
+
9
+ def url_for(obj)
10
+ "http://localhost:3000/posts/#{obj.id.to_s}"
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module ModelsForTesting
2
+ class Post
3
+ include Mongoid::Document
4
+
5
+ field :title, type: String
6
+ field :content, type: String
7
+ has_many :comments
8
+ validates_presence_of :title
9
+ end
10
+
11
+ class Comment
12
+ include Mongoid::Document
13
+ field :message, type: String
14
+ field :author, type: String
15
+ belongs_to :post
16
+
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: gizmo_test
5
+ hosts:
6
+ - localhost:27017
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gizmo-codescratch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Craig Wickesser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aquarium
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.5.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.5.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: database_cleaner
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: json
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Removing the crud from developing an API to support CRUD, in the name
111
+ of Gizmo.
112
+ email:
113
+ - craig@codescratch.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - LICENSE
119
+ - README.md
120
+ - lib/gizmo/aop/exception_handling.rb
121
+ - lib/gizmo/aop.rb
122
+ - lib/gizmo/base_operation.rb
123
+ - lib/gizmo/delete.rb
124
+ - lib/gizmo/find.rb
125
+ - lib/gizmo/find_all.rb
126
+ - lib/gizmo/make.rb
127
+ - lib/gizmo/models/context.rb
128
+ - lib/gizmo/models/error.rb
129
+ - lib/gizmo/models/response.rb
130
+ - lib/gizmo/models/validation_error.rb
131
+ - lib/gizmo/models.rb
132
+ - lib/gizmo/modify.rb
133
+ - lib/gizmo/search.rb
134
+ - lib/gizmo/version.rb
135
+ - lib/gizmo.rb
136
+ - spec/support/futz_controller.rb
137
+ - spec/support/models_for_testing.rb
138
+ - spec/support/mongoid.yml
139
+ homepage: http://codescratch.github.com/gizmo/
140
+ licenses:
141
+ - MIT
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ segments:
153
+ - 0
154
+ hash: -2973191327364084079
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ none: false
157
+ requirements:
158
+ - - ! '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ segments:
162
+ - 0
163
+ hash: -2973191327364084079
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 1.8.24
167
+ signing_key:
168
+ specification_version: 3
169
+ summary: Removing the crud from developing an API to support CRUD, in the name of
170
+ Gizmo.
171
+ test_files: []