license_finder 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +4 -3
  2. data/.travis.yml +1 -8
  3. data/bin/license_finder +31 -1
  4. data/db/migrate/201303290935_create_dependencies.rb +14 -0
  5. data/db/migrate/201303291155_create_licenses.rb +13 -0
  6. data/db/migrate/201303291402_create_approvals.rb +13 -0
  7. data/db/migrate/201303291456_create_ancestries.rb +9 -0
  8. data/db/migrate/201303291519_create_bundler_groups.rb +13 -0
  9. data/db/migrate/201303291720_move_manual_from_approvals_to_licenses.rb +11 -0
  10. data/db/migrate/201303291753_allow_null_license_names.rb +7 -0
  11. data/db/migrate/201304011027_allow_null_dependency_version.rb +7 -0
  12. data/db/migrate/201304020947_change_table_name_licenses_to_license_aliases.rb +5 -0
  13. data/features/approve_dependencies.feature +0 -45
  14. data/features/html_report.feature +1 -11
  15. data/features/license_finder.feature +13 -27
  16. data/features/license_finder_rake_task.feature +2 -1
  17. data/features/set_license.feature +2 -4
  18. data/features/step_definitions/license_finder_steps.rb +25 -0
  19. data/features/step_definitions/steps.rb +40 -26
  20. data/features/text_report.feature +2 -2
  21. data/files/license_finder.yml +1 -1
  22. data/lib/license_finder.rb +14 -6
  23. data/lib/license_finder/bundle.rb +4 -17
  24. data/lib/license_finder/bundle_syncer.rb +2 -3
  25. data/lib/license_finder/bundled_gem.rb +4 -47
  26. data/lib/license_finder/cli.rb +9 -16
  27. data/lib/license_finder/configuration.rb +55 -3
  28. data/lib/license_finder/dependency_report.rb +1 -1
  29. data/lib/license_finder/gem_saver.rb +69 -0
  30. data/lib/license_finder/html_report.rb +2 -2
  31. data/lib/license_finder/license.rb +60 -58
  32. data/lib/license_finder/license_files.rb +36 -0
  33. data/lib/license_finder/license_url.rb +8 -6
  34. data/lib/license_finder/platform.rb +32 -0
  35. data/lib/license_finder/possible_license_file.rb +1 -1
  36. data/lib/license_finder/tables.rb +7 -0
  37. data/lib/license_finder/tables/approval.rb +4 -0
  38. data/lib/license_finder/tables/bundler_group.rb +4 -0
  39. data/lib/license_finder/tables/dependency.rb +31 -0
  40. data/lib/license_finder/tables/license_alias.rb +22 -0
  41. data/lib/license_finder/yml_to_sql.rb +127 -0
  42. data/lib/tasks/license_finder.rake +3 -0
  43. data/lib/templates/html_report.erb +50 -32
  44. data/lib/templates/text_report.erb +3 -2
  45. data/license_finder.gemspec +14 -5
  46. data/readme.md +10 -50
  47. data/spec/lib/license_finder/bundle_spec.rb +22 -19
  48. data/spec/lib/license_finder/bundle_syncer_spec.rb +4 -10
  49. data/spec/lib/license_finder/bundled_gem_spec.rb +40 -108
  50. data/spec/lib/license_finder/cli_spec.rb +3 -3
  51. data/spec/lib/license_finder/configuration_spec.rb +53 -21
  52. data/spec/lib/license_finder/gem_saver_spec.rb +155 -0
  53. data/spec/lib/license_finder/html_report_spec.rb +32 -15
  54. data/spec/lib/license_finder/license_files_spec.rb +50 -0
  55. data/spec/lib/license_finder/tables/dependency_spec.rb +102 -0
  56. data/spec/lib/license_finder/tables/license_alias_spec.rb +54 -0
  57. data/spec/lib/license_finder/text_report_spec.rb +6 -4
  58. data/spec/lib/license_finder/yml_to_sql_spec.rb +99 -0
  59. data/spec/lib/license_finder_spec.rb +5 -5
  60. data/spec/spec_helper.rb +17 -1
  61. metadata +79 -32
  62. data/lib/license_finder/dependency.rb +0 -50
  63. data/lib/license_finder/persistence.rb +0 -1
  64. data/lib/license_finder/persistence/yaml.rb +0 -7
  65. data/lib/license_finder/persistence/yaml/configuration.rb +0 -34
  66. data/lib/license_finder/persistence/yaml/dependency.rb +0 -127
  67. data/lib/license_finder/source_syncer.rb +0 -40
  68. data/lib/templates/dependency.html.erb +0 -54
  69. data/spec/lib/license_finder/dependency_spec.rb +0 -188
  70. data/spec/lib/license_finder/persistence/yaml/dependency_spec.rb +0 -5
  71. data/spec/lib/license_finder/source_syncer_spec.rb +0 -37
  72. data/spec/support/shared_examples/persistence/configuration.rb +0 -28
  73. data/spec/support/shared_examples/persistence/dependency.rb +0 -138
@@ -9,7 +9,7 @@ Feature: Text Report
9
9
  | license | version |
10
10
  | MIT | 1.1.1 |
11
11
  When I run "license_finder"
12
- Then I should see the file "dependencies.txt" containing:
12
+ Then I should see the file "doc/dependencies.txt" containing:
13
13
  """
14
14
  descriptive_gem, 1.1.1, MIT
15
15
  """
@@ -21,7 +21,7 @@ Feature: Text Report
21
21
  | MIT | 1.1.1 |
22
22
  When I run "license_finder"
23
23
  And I run "license_finder"
24
- Then I should see the file "dependencies.txt" containing:
24
+ Then I should see the file "doc/dependencies.txt" containing:
25
25
  """
26
26
  descriptive_gem, 1.1.1, MIT
27
27
  """
@@ -5,4 +5,4 @@ whitelist:
5
5
  ignore_groups:
6
6
  #- test
7
7
  #- development
8
- dependencies_file_dir: './'
8
+ dependencies_file_dir: './doc/'
@@ -6,17 +6,15 @@ module LicenseFinder
6
6
  ROOT_PATH = Pathname.new(__FILE__).dirname
7
7
 
8
8
  DEPENDENCY_ATTRIBUTES = [
9
- "name", "source", "version", "license", "license_url", "approved", "notes",
9
+ "name", "version", "license", "license_url", "approved", "notes",
10
10
  "license_files", "bundler_groups", "summary",
11
- "description", "homepage", "children", "parents"
11
+ "description", "homepage", "children", "parents", "manual"
12
12
  ]
13
13
 
14
14
  autoload :Bundle, 'license_finder/bundle'
15
15
  autoload :BundledGem, 'license_finder/bundled_gem'
16
16
  autoload :CLI, 'license_finder/cli'
17
17
  autoload :Configuration, 'license_finder/configuration'
18
- autoload :Persistence, 'license_finder/persistence/yaml'
19
- autoload :Dependency, 'license_finder/dependency'
20
18
  autoload :License, 'license_finder/license'
21
19
  autoload :LicenseUrl, 'license_finder/license_url'
22
20
  autoload :PossibleLicenseFile, 'license_finder/possible_license_file'
@@ -25,10 +23,17 @@ module LicenseFinder
25
23
  autoload :TextReport, 'license_finder/text_report'
26
24
  autoload :Reporter, 'license_finder/reporter'
27
25
  autoload :BundleSyncer, 'license_finder/bundle_syncer'
28
- autoload :SourceSyncer, 'license_finder/source_syncer'
26
+ autoload :YmlToSql, 'license_finder/yml_to_sql'
27
+ autoload :Dependency, 'license_finder/tables/dependency'
28
+ autoload :Approval, 'license_finder/tables/approval'
29
+ autoload :LicenseAlias, 'license_finder/tables/license_alias'
30
+ autoload :BundlerGroup, 'license_finder/tables/bundler_group'
31
+ autoload :GemSaver, 'license_finder/gem_saver'
32
+ autoload :LicenseFiles, 'license_finder/license_files'
33
+ autoload :Platform, 'license_finder/platform'
29
34
 
30
35
  def self.config
31
- @config ||= Configuration.new
36
+ @config ||= Configuration.ensure_default
32
37
  end
33
38
 
34
39
  def self.load_rake_tasks
@@ -37,3 +42,6 @@ module LicenseFinder
37
42
  end
38
43
 
39
44
  require 'license_finder/railtie' if defined?(Rails)
45
+ require 'license_finder/tables'
46
+
47
+ LicenseFinder::YmlToSql.convert_if_required
@@ -2,6 +2,10 @@ module LicenseFinder
2
2
  class Bundle
3
3
  attr_writer :ignore_groups
4
4
 
5
+ def self.current_gem_dependencies(bundler_definition=nil)
6
+ new(bundler_definition).gems.map(&:save_or_merge)
7
+ end
8
+
5
9
  def initialize(bundler_definition=nil)
6
10
  @definition = bundler_definition || Bundler::Definition.build(gemfile_path, lockfile_path, nil)
7
11
  end
@@ -15,8 +19,6 @@ module LicenseFinder
15
19
  BundledGem.new(spec, dependency)
16
20
  end
17
21
 
18
- setup_parent_child_relationships
19
-
20
22
  @gems
21
23
  end
22
24
 
@@ -27,21 +29,6 @@ module LicenseFinder
27
29
  @ignore_groups ||= LicenseFinder.config.ignore_groups
28
30
  end
29
31
 
30
- def setup_parent_child_relationships
31
- dependency_index = {}
32
-
33
- gems.each do |dep|
34
- dependency_index[dep.dependency_name] = dep
35
- end
36
-
37
- gems.each do |dep|
38
- dep.children.each do |child_dep|
39
- license_finder_dependency = dependency_index[child_dep]
40
- license_finder_dependency.parents << dep.dependency_name if license_finder_dependency
41
- end
42
- end
43
- end
44
-
45
32
  def dependencies
46
33
  @dependencies ||= definition.dependencies
47
34
  end
@@ -3,9 +3,8 @@ module LicenseFinder
3
3
  extend self
4
4
 
5
5
  def sync!
6
- source_dependencies = Bundle.new.gems.map(&:to_dependency)
7
- target_dependencies = Dependency.all.select {|d| d.source == "bundle" }
8
- SourceSyncer.new(source_dependencies, target_dependencies).sync!
6
+ current_dependencies = Bundle.current_gem_dependencies
7
+ Dependency.destroy_obsolete(current_dependencies)
9
8
  end
10
9
  end
11
10
  end
@@ -1,8 +1,6 @@
1
1
  module LicenseFinder
2
2
  class BundledGem
3
- LICENSE_FILE_NAMES = %w(LICENSE License Licence COPYING README Readme ReadMe)
4
-
5
- attr_reader :parents
3
+ attr_reader :parents, :spec, :bundler_dependency
6
4
 
7
5
  def initialize(spec, bundler_dependency = nil)
8
6
  @spec = spec
@@ -29,22 +27,6 @@ module LicenseFinder
29
27
  @children ||= @spec.dependencies.collect(&:name)
30
28
  end
31
29
 
32
- def to_dependency
33
- @dependency ||= LicenseFinder::Dependency.new(
34
- 'name' => @spec.name,
35
- 'version' => @spec.version.to_s,
36
- 'license' => determine_license,
37
- 'license_files' => license_files.map(&:file_path),
38
- 'source' => 'bundle',
39
- 'bundler_groups' => (@bundler_dependency.groups if @bundler_dependency),
40
- 'summary' => @spec.summary,
41
- 'description' => @spec.description,
42
- 'homepage' => @spec.homepage,
43
- 'children' => children,
44
- 'parents' => parents
45
- )
46
- end
47
-
48
30
  def determine_license
49
31
  return @spec.license if @spec.license
50
32
 
@@ -52,40 +34,15 @@ module LicenseFinder
52
34
  end
53
35
 
54
36
  def license_files
55
- paths_with_license_names = find_matching_files(LICENSE_FILE_NAMES)
56
- paths_for_license_files = paths_with_license_names.map do |path|
57
- File.directory?(path) ? paths_for_files_in_license_directory(path) : path
58
- end.flatten.uniq
59
- get_files_for_paths(paths_for_license_files)
60
- end
61
-
62
- def install_path
63
- @spec.full_gem_path
37
+ LicenseFiles.new(@spec.full_gem_path).files
64
38
  end
65
39
 
66
40
  def sort_order
67
41
  dependency_name.downcase
68
42
  end
69
43
 
70
- private
71
-
72
- def find_matching_files(names)
73
- Dir.glob(File.join(install_path, '**', "*{#{names.join(',')}}*"))
74
- end
75
-
76
- def get_file_for_path(path)
77
- PossibleLicenseFile.new(install_path, path)
78
- end
79
-
80
- def paths_for_files_in_license_directory(path)
81
- entries_in_directory = Dir::entries(path).reject { |p| p.match(/^(\.){1,2}$/) }
82
- entries_in_directory.map { |entry_name| File.join(path, entry_name) }
83
- end
84
-
85
- def get_files_for_paths(paths_for_license_files)
86
- paths_for_license_files.map do |path|
87
- get_file_for_path(path)
88
- end
44
+ def save_or_merge
45
+ GemSaver.find_or_initialize_by_name(@spec.name, self).save
89
46
  end
90
47
  end
91
48
  end
@@ -2,13 +2,16 @@ module LicenseFinder
2
2
  module CLI
3
3
  extend self
4
4
 
5
+ @@run_complete = false
6
+
5
7
  def check_for_action_items
6
- create_default_configuration
7
8
  BundleSyncer.sync!
9
+ @@run_complete = true
8
10
  generate_reports
9
11
 
10
12
  unapproved = Dependency.unapproved
11
13
 
14
+ puts "\r" + " "*24
12
15
  if unapproved.count == 0
13
16
  puts "All gems are approved for use"
14
17
  else
@@ -19,18 +22,18 @@ module LicenseFinder
19
22
  end
20
23
 
21
24
  def execute!(options={})
22
- create_default_configuration
23
-
24
25
  if options.empty?
25
26
  check_for_action_items
26
27
  else
27
- dependency = Dependency.find_by_name(options[:dependency])
28
+ dependency = Dependency.first(name: options[:dependency])
28
29
 
30
+ @@run_complete = true
31
+ puts "\r" + " "*24
29
32
  if options[:approve]
30
33
  dependency.approve!
31
34
  puts "The #{dependency.name} has been approved!\n\n"
32
35
  elsif options[:license]
33
- dependency.update_attributes :license => options[:license]
36
+ dependency.set_license_manually options[:license]
34
37
  puts "The #{dependency.name} has been marked as using #{options[:license]} license!\n\n"
35
38
  end
36
39
 
@@ -40,17 +43,7 @@ module LicenseFinder
40
43
 
41
44
  private
42
45
  def generate_reports
43
- LicenseFinder::Reporter.write_reports
44
- end
45
-
46
- def create_default_configuration
47
- unless File.exists?(LicenseFinder.config.config_file_path)
48
- FileUtils.mkdir_p(File.join('.', 'config'))
49
- FileUtils.cp(
50
- File.join(File.dirname(__FILE__), '..', '..', 'files', 'license_finder.yml'),
51
- LicenseFinder.config.config_file_path
52
- )
53
- end
46
+ Reporter.write_reports
54
47
  end
55
48
  end
56
49
  end
@@ -1,7 +1,58 @@
1
1
  module LicenseFinder
2
- class Configuration < LicenseFinder::Persistence::Configuration
2
+ class Configuration
3
+ attr_accessor :whitelist, :ignore_groups, :dependencies_dir
4
+
5
+ def self.config_file_path
6
+ File.join('.', 'config', 'license_finder.yml')
7
+ end
8
+
9
+ def self.ensure_default
10
+ make_config_file unless File.exists?(config_file_path)
11
+ new
12
+ end
13
+
14
+ def self.make_config_file
15
+ FileUtils.mkdir_p(File.join('.', 'config'))
16
+ FileUtils.cp(
17
+ File.join(File.dirname(__FILE__), '..', '..', 'files', 'license_finder.yml'),
18
+ config_file_path
19
+ )
20
+ end
21
+
22
+ def initialize(config={})
23
+ if File.exists?(config_file_path)
24
+ yaml = File.read(config_file_path)
25
+ config = YAML.load(yaml).merge config
26
+ end
27
+
28
+ @whitelist = config['whitelist'] || []
29
+ @ignore_groups = (config["ignore_groups"] || []).map(&:to_sym)
30
+ @dependencies_dir = config['dependencies_file_dir'] || './doc/'
31
+ FileUtils.mkdir_p(@dependencies_dir)
32
+ end
33
+
34
+ def config_file_path
35
+ self.class.config_file_path
36
+ end
37
+
38
+ def database_path
39
+ File.expand_path(File.join(dependencies_dir, "dependencies.db"))
40
+ end
41
+
42
+ def dependencies_yaml
43
+ File.join(dependencies_dir, "dependencies.yml")
44
+ end
45
+
46
+ def dependencies_text
47
+ File.join(dependencies_dir, "dependencies.txt")
48
+ end
49
+
50
+ def dependencies_html
51
+ File.join(dependencies_dir, "dependencies.html")
52
+ end
53
+
3
54
  def ignore_groups
4
- super.map &:to_sym
55
+ @ignore_groups.map &:to_sym
5
56
  end
6
57
 
7
58
  def whitelisted?(license_name)
@@ -10,9 +61,10 @@ module LicenseFinder
10
61
  end
11
62
 
12
63
  private
64
+
13
65
  def whitelisted_licenses
14
66
  whitelist.map do |license_name|
15
- LicenseFinder::License.find_by_name(license_name) || license_name
67
+ License.find_by_name(license_name) || license_name
16
68
  end.compact
17
69
  end
18
70
  end
@@ -16,7 +16,7 @@ module LicenseFinder
16
16
 
17
17
  def to_s
18
18
  filename = File.join(File.dirname(__FILE__), '..', 'templates', "#{self.class.underscored_name}.erb")
19
- template = ERB.new(File.read(filename))
19
+ template = ERB.new(File.read(filename), 0, '-')
20
20
  template.result(binding)
21
21
  end
22
22
 
@@ -0,0 +1,69 @@
1
+ module LicenseFinder
2
+ class GemSaver
3
+ extend Forwardable
4
+ def_delegators :spec, :name, :version, :summary, :description, :homepage
5
+ def_delegators :bundled_gem, :bundler_dependency, :determine_license, :children
6
+
7
+ def self.find_or_initialize_by_name(name, bundled_gem)
8
+ dependency = Dependency.find_or_create(name: name) do |d|
9
+ d.approval = Approval.create
10
+ end
11
+ new(dependency, bundled_gem)
12
+ end
13
+
14
+ def initialize(dependency, bundled_gem)
15
+ @bundled_gem = bundled_gem
16
+ @dependency = dependency
17
+ end
18
+
19
+ def save
20
+ DB.transaction do
21
+ apply_dependency_definition
22
+ refresh_bundler_groups
23
+ refresh_children
24
+ apply_better_license
25
+ end
26
+ dependency
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :dependency, :bundled_gem
32
+
33
+ def spec
34
+ bundled_gem.spec
35
+ end
36
+
37
+ def apply_dependency_definition
38
+ dependency.version = version.to_s
39
+ dependency.summary = summary
40
+ dependency.description = description
41
+ dependency.homepage = homepage
42
+ dependency.license ||= LicenseAlias.create(name: determine_license)
43
+ dependency.save
44
+ end
45
+
46
+ def refresh_bundler_groups
47
+ dependency.remove_all_bundler_groups
48
+ if bundler_dependency
49
+ bundler_dependency.groups.each { |group|
50
+ dependency.add_bundler_group BundlerGroup.find_or_create(name: group.to_s)
51
+ }
52
+ end
53
+ end
54
+
55
+ def refresh_children
56
+ dependency.remove_all_children
57
+ children.each do |child|
58
+ dependency.add_child Dependency.find_or_create(name: child.to_s)
59
+ end
60
+ end
61
+
62
+ def apply_better_license
63
+ if dependency.license && !dependency.license.manual && determine_license != 'other'
64
+ dependency.license.name = determine_license
65
+ dependency.license.save
66
+ end
67
+ end
68
+ end
69
+ end
@@ -4,11 +4,11 @@ module LicenseFinder
4
4
  class HtmlReport < DependencyReport
5
5
  private
6
6
  def unapproved_dependencies
7
- dependencies.reject(&:approved)
7
+ dependencies.reject(&:approved?)
8
8
  end
9
9
 
10
10
  def grouped_dependencies
11
- dependencies.group_by(&:license).sort_by { |_, group| group.size }.reverse
11
+ dependencies.group_by {|dep| dep.license.name }.sort_by { |_, group| group.size }.reverse
12
12
  end
13
13
  end
14
14
  end
@@ -1,84 +1,86 @@
1
- module LicenseFinder::License
2
- class << self
3
- def all
4
- @all ||= []
5
- end
1
+ module LicenseFinder
2
+ module License
3
+ class << self
4
+ def all
5
+ @all ||= []
6
+ end
6
7
 
7
- def find_by_name(license_name)
8
- all.detect { |l| l.names.map(&:downcase).include? license_name.to_s.downcase }
8
+ def find_by_name(license_name)
9
+ all.detect { |l| l.names.map(&:downcase).include? license_name.to_s.downcase }
10
+ end
9
11
  end
10
- end
11
12
 
12
- class Text
13
- def initialize(text)
14
- @text = normalized(text)
15
- end
13
+ class Text
14
+ def initialize(text)
15
+ @text = normalized(text)
16
+ end
16
17
 
17
- def to_s
18
- @text
19
- end
18
+ def to_s
19
+ @text
20
+ end
20
21
 
21
- private
22
+ private
22
23
 
23
- def normalized(text)
24
- text.gsub(/\s+/, ' ').gsub(/['`"]{1,2}/, "\"")
24
+ def normalized(text)
25
+ text.gsub(/\s+/, ' ').gsub(/['`"]{1,2}/, "\"")
26
+ end
25
27
  end
26
- end
27
28
 
28
- class Base
29
- class << self
30
- attr_accessor :license_url, :alternative_names
29
+ class Base
30
+ class << self
31
+ attr_accessor :license_url, :alternative_names
31
32
 
32
- def inherited(descendant)
33
- LicenseFinder::License.all << descendant
34
- end
33
+ def inherited(descendant)
34
+ License.all << descendant
35
+ end
35
36
 
36
- def names
37
- ([demodulized_name, pretty_name] + self.alternative_names).uniq
38
- end
37
+ def names
38
+ ([demodulized_name, pretty_name] + self.alternative_names).uniq
39
+ end
39
40
 
40
- def alternative_names
41
- @alternative_names ||= []
42
- end
41
+ def alternative_names
42
+ @alternative_names ||= []
43
+ end
43
44
 
44
- def demodulized_name
45
- name.gsub(/^.*::/, '')
46
- end
45
+ def demodulized_name
46
+ name.gsub(/^.*::/, '')
47
+ end
47
48
 
48
- def slug
49
- demodulized_name.downcase
50
- end
49
+ def slug
50
+ demodulized_name.downcase
51
+ end
51
52
 
52
- def pretty_name
53
- demodulized_name
54
- end
53
+ def pretty_name
54
+ demodulized_name
55
+ end
55
56
 
56
- def license_text
57
- unless defined?(@license_text)
58
- template = File.join(LicenseFinder::ROOT_PATH, "data", "licenses", "#{demodulized_name}.txt").to_s
57
+ def license_text
58
+ unless defined?(@license_text)
59
+ template = File.join(ROOT_PATH, "data", "licenses", "#{demodulized_name}.txt").to_s
59
60
 
60
- @license_text = Text.new(File.read(template)).to_s if File.exists?(template)
61
+ @license_text = Text.new(File.read(template)).to_s if File.exists?(template)
62
+ end
63
+ @license_text
61
64
  end
62
- @license_text
63
- end
64
65
 
65
- def license_regex
66
- /#{Regexp.escape(license_text).gsub(/<[^<>]+>/, '(.*)')}/ if license_text
66
+ def license_regex
67
+ /#{Regexp.escape(license_text).gsub(/<[^<>]+>/, '(.*)')}/ if license_text
68
+ end
67
69
  end
68
- end
69
70
 
70
- def initialize(text)
71
- self.text = text
72
- end
71
+ def initialize(text)
72
+ self.text = text
73
+ end
73
74
 
74
- attr_reader :text
75
+ attr_reader :text
75
76
 
76
- def text=(text)
77
- @text = Text.new(text).to_s
78
- end
77
+ def text=(text)
78
+ @text = Text.new(text).to_s
79
+ end
79
80
 
80
- def matches?
81
- !!(text =~ self.class.license_regex if self.class.license_regex)
81
+ def matches?
82
+ !!(text =~ self.class.license_regex if self.class.license_regex)
83
+ end
82
84
  end
83
85
  end
84
86
  end