radiant-language_redirect-extension 1.0.0

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/README.textile ADDED
@@ -0,0 +1,52 @@
1
+ h1. Language Redirect
2
+
3
+ Redirects to the appropriate language section based on the content encoding preferred by the Web browser.
4
+
5
+ h2. Version History
6
+
7
+ * 2/2013 - Radiant 1.1.x compatible, fix deprecations, gemify
8
+ * 5/2011 - Radiant 1.0 compatible
9
+ * 4/2009 - Radiant 0.8 compatible
10
+ * 2/2009 - Radiant 0.7 compatible
11
+ * 1/2007 - Intital Import
12
+
13
+ h2. Installation
14
+
15
+ * add to your Gemfile: <code>gem "radiant-language_redirect-extension"</code>
16
+ * run bundle
17
+
18
+ h2. Usage
19
+
20
+ You can add a "config" part to the page which maps languages to URLs
21
+ in the following format:
22
+
23
+ <pre>
24
+ lang: url
25
+ lang: url
26
+ ...
27
+ </pre>
28
+
29
+ Where "lang" refers to a language code and "url" refers to the URL
30
+ which should be redirected to based on the preferred content
31
+ encoding of the Web browser.
32
+
33
+ The following listing is a sample "config" page part:
34
+ <pre>
35
+ en: /en/
36
+ ja: /ja/
37
+ *: /en/
38
+ </pre>
39
+ In this example, when the browser prefers English content it will be
40
+ redirected to the "/en/" URL. When it prefers Japanese content it will
41
+ be redirected to the "/ja/" URL. In the event that the browser prefers
42
+ something other than English or Japanese content, they will be
43
+ redirected to the "/en/" URL. This is what the "*" in the last entry
44
+ does. URLs can be either relative (without "http://hostname.tld") or
45
+ absolute (with "http://hostname.tld").
46
+
47
+ If no "config" part is specified the behavior will force the page to
48
+ redirect to the "/en/" folder.
49
+
50
+ h2. Credits
51
+
52
+ Created by "Giovanni Intini":http://tempe.st/about/, maintained by "contributors":https://github.com/intinig/radiant_language_redirect_extension/network
data/Rakefile ADDED
@@ -0,0 +1,137 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
5
+ gem.name = "radiant-language_redirect-extension"
6
+ gem.homepage = "https://github.com/avonderluft/radiant-language_redirect-extension"
7
+ gem.license = "MIT"
8
+ gem.summary = %Q{Language Redirect Extension for Radiant CMS}
9
+ gem.description = %Q{Redirects to the appropriate language section based on the content encoding preferred by the Web browser.}
10
+ gem.email = "avonderluft@avlux.net"
11
+ gem.authors = ["Giovanni Intini", "Andrew vonderLuft"]
12
+ gem.add_dependency 'radiant', ">=1.1.3"
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. This is only required if you plan to package language_redirect as a gem."
16
+ end
17
+
18
+ # I think this is the one that should be moved to the extension Rakefile template
19
+
20
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
21
+ # Check to see if the rspec plugin is installed first and require
22
+ # it if it is. If not, use the gem version.
23
+
24
+ # Determine where the RSpec plugin is by loading the boot
25
+ unless defined? RADIANT_ROOT
26
+ ENV["RAILS_ENV"] = "test"
27
+ case
28
+ when ENV["RADIANT_ENV_FILE"]
29
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
30
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
31
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
32
+ else
33
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
34
+ end
35
+ end
36
+
37
+ require 'rake'
38
+ require 'rdoc/task'
39
+ require 'rake/testtask'
40
+
41
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
42
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
43
+ require 'spec/rake/spectask'
44
+ # require 'spec/translator'
45
+
46
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
47
+ Object.send(:remove_const, :RADIANT_ROOT)
48
+
49
+ extension_root = File.expand_path(File.dirname(__FILE__))
50
+
51
+ task :default => :spec
52
+ task :stats => "spec:statsetup"
53
+
54
+ desc "Run all specs in spec directory"
55
+ Spec::Rake::SpecTask.new(:spec) do |t|
56
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
57
+ t.spec_files = FileList['spec/**/*_spec.rb']
58
+ end
59
+
60
+ namespace :spec do
61
+ desc "Run all specs in spec directory with RCov"
62
+ Spec::Rake::SpecTask.new(:rcov) do |t|
63
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
64
+ t.spec_files = FileList['spec/**/*_spec.rb']
65
+ t.rcov = true
66
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
67
+ end
68
+
69
+ desc "Print Specdoc for all specs"
70
+ Spec::Rake::SpecTask.new(:doc) do |t|
71
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
72
+ t.spec_files = FileList['spec/**/*_spec.rb']
73
+ end
74
+
75
+ [:models, :controllers, :views, :helpers].each do |sub|
76
+ desc "Run the specs under spec/#{sub}"
77
+ Spec::Rake::SpecTask.new(sub) do |t|
78
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
79
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
80
+ end
81
+ end
82
+
83
+ # Hopefully no one has written their extensions in pre-0.9 style
84
+ # desc "Translate specs from pre-0.9 to 0.9 style"
85
+ # task :translate do
86
+ # translator = ::Spec::Translator.new
87
+ # dir = RAILS_ROOT + '/spec'
88
+ # translator.translate(dir, dir)
89
+ # end
90
+
91
+ # Setup specs for stats
92
+ task :statsetup do
93
+ require 'code_statistics'
94
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
95
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
96
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
97
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
98
+ ::CodeStatistics::TEST_TYPES << "Model specs"
99
+ ::CodeStatistics::TEST_TYPES << "View specs"
100
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
101
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
102
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
103
+ end
104
+
105
+ namespace :db do
106
+ namespace :fixtures do
107
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
108
+ task :load => :environment do
109
+ require 'active_record/fixtures'
110
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
111
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
112
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ desc 'Generate documentation for the language_redirect extension.'
120
+ Rake::RDocTask.new(:rdoc) do |rdoc|
121
+ rdoc.rdoc_dir = 'rdoc'
122
+ rdoc.title = 'LanguageRedirectExtension'
123
+ rdoc.options << '--line-numbers' << '--inline-source'
124
+ rdoc.rdoc_files.include('README')
125
+ rdoc.rdoc_files.include('lib/**/*.rb')
126
+ end
127
+
128
+ # For extensions that are in transition
129
+ desc 'Test the language_redirect extension.'
130
+ Rake::TestTask.new(:test) do |t|
131
+ t.libs << 'lib'
132
+ t.pattern = 'test/**/*_test.rb'
133
+ t.verbose = true
134
+ end
135
+
136
+ # Load any custom rakefiles for extension
137
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,66 @@
1
+ class LanguageRedirectPage < Page
2
+
3
+ def headers
4
+ {
5
+ 'Status' => "301 Redirect",
6
+ 'Location' => location,
7
+ 'Vary' => "Accept-Language"
8
+ }
9
+ end
10
+
11
+ def render
12
+ "<html><body>301 Redirect</body></html>"
13
+ end
14
+
15
+ def cache?
16
+ false
17
+ end
18
+
19
+ def response_code
20
+ 301
21
+ end
22
+
23
+ def find_by_url(url, live=true, clean=true)
24
+ found = super
25
+ if (found.nil? || found.is_a?(FileNotFoundPage)) &&
26
+ location_map.values.all? {|target| clean_url(url) !~ Regexp.new(target) }
27
+ self
28
+ else
29
+ found
30
+ end
31
+ end
32
+ alias find_by_path find_by_url
33
+
34
+ protected
35
+ def config
36
+ YAML.load(render_part('config'))
37
+ end
38
+
39
+ def languages
40
+ langs = (@request.env["HTTP_ACCEPT_LANGUAGE"] || "").split(/[,\s]+/)
41
+ langs_with_weights = langs.map do |ele|
42
+ both = ele.split(/;q=/)
43
+ lang = both[0].split('-').first
44
+ weight = both[1] ? Float(both[1]) : 1
45
+ [-weight, lang]
46
+ end.sort_by(&:first).map(&:last)
47
+ end
48
+
49
+ def location
50
+ path = location_map[languages.find { |lang| location_map[lang] }]
51
+ path ||= location_map["*"] || '/en/'
52
+ path += request.request_uri
53
+ path.gsub!(%r{([^:])//}, '\1/')
54
+ if path =~ %r{[:][/][/]}
55
+ path
56
+ else
57
+ path.sub!(%r{^([^/])}, '/\1')
58
+ @request.protocol + @request.host_with_port + path
59
+ end
60
+ end
61
+
62
+ def location_map
63
+ @location_map ||= config
64
+ end
65
+
66
+ end
@@ -0,0 +1,11 @@
1
+ class LanguageRedirectExtension < Radiant::Extension
2
+ version "#{File.read(File.expand_path(File.dirname(__FILE__)) + '/VERSION')}"
3
+ description "Enables redirects to the appropriate language section based on the content encoding preferred by the Web browser."
4
+ url "https://github.com/avonderluft/radiant_language_redirect_extension"
5
+
6
+ def activate
7
+ Page.send :include, LanguageRedirectTags
8
+ LanguageRedirectPage
9
+ end
10
+
11
+ end
@@ -0,0 +1,90 @@
1
+ module LanguageRedirectTags
2
+ include Radiant::Taggable
3
+
4
+ class TagError < StandardError; end
5
+
6
+ desc %{
7
+ Shows the content if the code matches the actual language
8
+
9
+ *Usage:*
10
+ <pre><code><r:if_language code="language" >...</r:if_language></code></pre>
11
+ }
12
+ tag 'if_language' do |tag|
13
+ tag.expand if tag.attr['code'] == current_language
14
+ end
15
+
16
+ desc %{
17
+ Shows the content unless the code matches the actual language
18
+
19
+ *Usage:*
20
+ <pre><code><r:unless_language code="language" >...</r:unless_language></code></pre>
21
+ }
22
+ tag 'unless_language' do |tag|
23
+ tag.expand unless tag.attr['code'] == current_language
24
+ end
25
+
26
+ desc %{
27
+ Shows a language chooser list separated by the characters supplied with the 'separator' attribute. By default, 'separator' is set to |.
28
+ If the 'upcase' attribute is set to false, the language description will be uppercase. By default, 'upcase' is set to true.
29
+ If the 'singleletter' attribute is set to false, the language description will show only the first letter. By default, 'singleletter' is set to true.
30
+
31
+ *Usage:*
32
+ <pre><code><r:choose_language [separator="|"] [upcase="true|false] [singleletter="true|false"] /></code></pre>
33
+ }
34
+ tag 'languageswitch' do |tag|
35
+ upcase = tag.attr.has_key?('upcase') && tag.attr['upcase']=='false' ? nil : true
36
+ singleletter = tag.attr.has_key?('singleletter') && tag.attr['singleletter']=='false' ? nil : true
37
+ separator = tag.attr['separator'] || "|"
38
+
39
+ html = '<ul>'
40
+ config = language_config(self)
41
+ config.delete('*')
42
+ position = 1
43
+ config.each do |key, value|
44
+ key = key.upcase if upcase
45
+ key = key.split(//)[0] if singleletter
46
+ if request.request_uri.starts_with? value
47
+ html += "<li class='active_language'>#{key}</li>"
48
+ else
49
+ html += "<li><a href='#{value}'>#{key}</a></li>"
50
+ end
51
+ html += "<li>#{separator}</li>" if position != config.length
52
+ position = position + 1
53
+ end
54
+ html += '</ul>'
55
+ end
56
+
57
+ tag 'breadcrumbs' do |tag|
58
+ page = tag.locals.page
59
+ breadcrumbs = [tag.render('breadcrumb')]
60
+ nolinks = (tag.attr['nolinks'] == 'true')
61
+ # Remove LanguageRedirect pages from the hierarchy (preferably the root)
62
+ page.ancestors.reject {|p| p.is_a?(LanguageRedirectPage) }.each do |ancestor|
63
+ tag.locals.page = ancestor
64
+ if nolinks
65
+ breadcrumbs.unshift tag.render('breadcrumb')
66
+ else
67
+ breadcrumbs.unshift %{<a href="#{tag.render('url')}">#{tag.render('breadcrumb')}</a>}
68
+ end
69
+ end
70
+ separator = tag.attr['separator'] || ' &gt; '
71
+ breadcrumbs.join(separator)
72
+ end
73
+
74
+ protected
75
+
76
+ def current_language
77
+ config = language_config(self)
78
+ config.each do |key, value|
79
+ return key if request.request_uri.starts_with? value
80
+ end
81
+ end
82
+
83
+ def language_config(page)
84
+ if page.class == LanguageRedirectPage
85
+ YAML.load(page.render_part(:config))
86
+ elsif page.parent
87
+ language_config(page.parent)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,17 @@
1
+ namespace :radiant do
2
+ namespace :extensions do
3
+ namespace :language_redirect do
4
+
5
+ desc "Runs the migration of the Language Redirect extension"
6
+ task :migrate do
7
+ require 'extension_migrator'
8
+ if ENV["VERSION"]
9
+ LanguageRedirectExtension.migrator.migrate(ENV["VERSION"].to_i)
10
+ else
11
+ LanguageRedirectExtension.migrator.migrate
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ class LanguageRedirectDataset < Dataset::Base
2
+ uses :pages
3
+
4
+ def load
5
+ Page.update_all({:class_name => "LanguageRedirectPage"}, "id = #{page_id(:home)}")
6
+ end
7
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe "LanguageRedirectPage" do
4
+
5
+ before :each do
6
+ @request = ActionController::TestRequest.new
7
+ @response = ActionController::TestResponse.new
8
+ @page = LanguageRedirectPage.new
9
+ @page.stub!(:url).and_return('/')
10
+ @page.parts.build(:name => 'config', :content => {'de' => '/de/', 'es' => '/es/', '*' => '/en/'}.to_yaml)
11
+ @page.parts[0].draft_content = @page.parts[0].content if defined? ConcurrentDraft
12
+ @request.request_uri = '/'
13
+ end
14
+
15
+ it "should select the * entry when no language is given" do
16
+ @page.process(@request, @response)
17
+ @response.should be_redirect
18
+ @response.redirect_url.should == "http://test.host/en/"
19
+ end
20
+
21
+ it "should select the * entry when no language matches" do
22
+ @request.env['HTTP_ACCEPT_LANGUAGE'] = "jp, fr"
23
+ @page.process(@request, @response)
24
+ @response.should be_redirect
25
+ @response.redirect_url.should == "http://test.host/en/"
26
+ end
27
+
28
+ it "should select the first matching language in order given (when all weights are same)" do
29
+ @request.env['HTTP_ACCEPT_LANGUAGE'] = "es, de, en"
30
+ @page.process(@request, @response)
31
+ @response.should be_redirect
32
+ @response.redirect_url.should == "http://test.host/es/"
33
+ end
34
+
35
+ it "should select the first matching language with the highest weight" do
36
+ @request.env['HTTP_ACCEPT_LANGUAGE'] = "de;q=0.7, es;q=0.9, en;q=0.2"
37
+ @page.process(@request, @response)
38
+ @response.should be_redirect
39
+ @response.redirect_url.should == "http://test.host/es/"
40
+ end
41
+
42
+ it "should append the request URI to the redirect location, cleaning up extra slashes" do
43
+ @request.request_uri = '/freight/'
44
+ @page.process(@request, @response)
45
+ @response.redirect_url.should == "http://test.host/en/freight/"
46
+ end
47
+
48
+ describe "when a child page is not found" do
49
+ dataset :language_redirect
50
+
51
+ before :each do
52
+ @page = pages(:home)
53
+ @page.slug = "freight"
54
+ @page.save!
55
+ @page.parts.create!(:name => 'config', :content => {'de' => '/parent/', 'es' => '/es/', '*' => '/en/'}.to_yaml)
56
+ end
57
+
58
+ it "should return self if the URL doesn't match any of the redirect locations" do
59
+ Page.find_by_path('/freight/').should == @page
60
+ end
61
+
62
+ it "should return the found page or nil if the URL matches a redirect location" do
63
+ Page.find_by_path('/en/freight/').should_not == @page
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Adjusted breadcrumbs tag" do
4
+ dataset :pages
5
+
6
+ before :each do
7
+ pages(:home).update_attributes(:class_name => "LanguageRedirectPage")
8
+ end
9
+
10
+ it "should not render the language-redirect page (homepage) in the breadcrumbs" do
11
+ pages(:child_2).should render('<r:breadcrumbs nolinks="true" />').as('Parent &gt; Child 2')
12
+ end
13
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,39 @@
1
+ unless defined? RADIANT_ROOT
2
+ ENV["RAILS_ENV"] = "test"
3
+ case
4
+ when ENV["RADIANT_ENV_FILE"]
5
+ require ENV["RADIANT_ENV_FILE"]
6
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
7
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
8
+ else
9
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
10
+ end
11
+ end
12
+ require "#{RADIANT_ROOT}/spec/spec_helper"
13
+
14
+ Dataset::Resolver.default << (File.dirname(__FILE__) + "/datasets")
15
+ Dataset::Resolver.default << (SnippetsExtension.root + "/spec/datasets") if defined?(SnippetsExtension)
16
+ if File.directory?(File.dirname(__FILE__) + "/matchers")
17
+ Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
18
+ end
19
+ if File.directory?(File.dirname(__FILE__) + "/matchers")
20
+ Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
21
+ end
22
+
23
+ Spec::Runner.configure do |config|
24
+ # config.use_transactional_fixtures = true
25
+ # config.use_instantiated_fixtures = false
26
+ # config.fixture_path = RAILS_ROOT + '/spec/fixtures'
27
+
28
+ # You can declare fixtures for each behaviour like this:
29
+ # describe "...." do
30
+ # fixtures :table_a, :table_b
31
+ #
32
+ # Alternatively, if you prefer to declare them only once, you can
33
+ # do so here, like so ...
34
+ #
35
+ # config.global_fixtures = :table_a, :table_b
36
+ #
37
+ # If you declare global fixtures, be aware that they will be declared
38
+ # for all of your examples, even those that don't use them.
39
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radiant-language_redirect-extension
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Giovanni Intini
14
+ - Andrew vonderLuft
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2013-02-14 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 21
28
+ segments:
29
+ - 1
30
+ - 1
31
+ - 3
32
+ version: 1.1.3
33
+ version_requirements: *id001
34
+ name: radiant
35
+ prerelease: false
36
+ type: :runtime
37
+ description: Redirects to the appropriate language section based on the content encoding preferred by the Web browser.
38
+ email: avonderluft@avlux.net
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - README.textile
45
+ files:
46
+ - README.textile
47
+ - Rakefile
48
+ - VERSION
49
+ - app/models/language_redirect_page.rb
50
+ - language_redirect_extension.rb
51
+ - lib/language_redirect_tags.rb
52
+ - lib/tasks/language_redirect_extension_tasks.rake
53
+ - spec/datasets/language_redirect_dataset.rb
54
+ - spec/models/language_redirect_page_spec.rb
55
+ - spec/models/tags_spec.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ homepage: https://github.com/avonderluft/radiant-language_redirect-extension
59
+ licenses:
60
+ - MIT
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.25
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Language Redirect Extension for Radiant CMS
91
+ test_files: []
92
+