fontist 1.8.5 → 1.8.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +13 -13
  3. data/fontist.gemspec +3 -7
  4. data/lib/fontist/cli.rb +10 -4
  5. data/lib/fontist/font.rb +28 -15
  6. data/lib/fontist/font_installer.rb +40 -39
  7. data/lib/fontist/formula.rb +4 -0
  8. data/lib/fontist/import/helpers/system_helper.rb +1 -3
  9. data/lib/fontist/import/recursive_extraction.rb +25 -119
  10. data/lib/fontist/manifest/install.rb +11 -3
  11. data/lib/fontist/utils.rb +0 -8
  12. data/lib/fontist/utils/cache.rb +27 -8
  13. data/lib/fontist/utils/downloader.rb +81 -36
  14. data/lib/fontist/version.rb +1 -1
  15. metadata +14 -89
  16. data/lib/fontist/import/extractors.rb +0 -9
  17. data/lib/fontist/import/extractors/cab_extractor.rb +0 -37
  18. data/lib/fontist/import/extractors/cpio_extractor.rb +0 -39
  19. data/lib/fontist/import/extractors/extractor.rb +0 -19
  20. data/lib/fontist/import/extractors/gzip_extractor.rb +0 -27
  21. data/lib/fontist/import/extractors/ole_extractor.rb +0 -41
  22. data/lib/fontist/import/extractors/rpm_extractor.rb +0 -45
  23. data/lib/fontist/import/extractors/seven_zip_extractor.rb +0 -44
  24. data/lib/fontist/import/extractors/tar_extractor.rb +0 -47
  25. data/lib/fontist/import/extractors/zip_extractor.rb +0 -31
  26. data/lib/fontist/utils/cpio/cpio.rb +0 -199
  27. data/lib/fontist/utils/cpio_extractor.rb +0 -47
  28. data/lib/fontist/utils/exe_extractor.rb +0 -75
  29. data/lib/fontist/utils/gzip_extractor.rb +0 -24
  30. data/lib/fontist/utils/msi_extractor.rb +0 -31
  31. data/lib/fontist/utils/rpm_extractor.rb +0 -37
  32. data/lib/fontist/utils/seven_zip_extractor.rb +0 -41
  33. data/lib/fontist/utils/tar_extractor.rb +0 -61
  34. data/lib/fontist/utils/zip_extractor.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b392969d430a5af69c38a73069d5a98cf90a6ef1c246f8cca215a7cd4c1202c
4
- data.tar.gz: 97ca59729bf50acefa0bfe5ab23b82050eed8fd6ba4782a6d814a77e44939b4b
3
+ metadata.gz: 5a61fbcb4c7c4242df9816a14e4b38ef2a0653f2979d60351ff46e2afd534287
4
+ data.tar.gz: 117b390025eb49b700213b9964aa7134c39d9945b1aed822e7b92e36295b750f
5
5
  SHA512:
6
- metadata.gz: b5911d154187149d2d8ebc81e776b7c4b12e0a8eddf4f2540f3914b41fd29c582a29287b5ed1d3d7cccbe696aeb40908f5039bf350c281e315ac08fffe76e110
7
- data.tar.gz: 49a7e53eff6d6b750248141a608cb3e92c03c19130f75f4ff8806c7e55ca0d478cec376ed9eaa273dedff3e05b31057892c0c47367a48f1a3eadd5599ecaf968
6
+ metadata.gz: c77e83a9a67f3782bbf4eb8c3d3de30ebb0db904c6429e6d83bc457e9651125acee7528b5503ab51fac3dfc6a4441e966cc8596aa70455dc1181f2c866f0cdae
7
+ data.tar.gz: 1b542a3e06a395198277e5ac34d7615fa25f95bd7da1a3fcbd6e819a572871b3d0ca4ad51678859eeae180c0b76b586d6c815b1807ac82b3f79e703eef5bffde
@@ -13,19 +13,19 @@ jobs:
13
13
  strategy:
14
14
  fail-fast: false
15
15
  matrix:
16
- ruby: [ '2.6', '2.5', '2.4' ]
16
+ ruby: [ '2.4', '2.5', '2.6', '2.7' ]
17
17
  os: [ ubuntu-latest, windows-latest, macos-latest ]
18
18
  experimental: [ false ]
19
- include:
20
- - ruby: '2.7'
21
- os: 'ubuntu-latest'
22
- experimental: true
23
- - ruby: '2.7'
24
- os: 'windows-latest'
25
- experimental: true
26
- - ruby: '2.7'
27
- os: 'macos-latest'
28
- experimental: true
19
+ # include:
20
+ # - ruby: '3.0'
21
+ # os: 'ubuntu-latest'
22
+ # experimental: true
23
+ # - ruby: '3.0'
24
+ # os: 'windows-latest'
25
+ # experimental: true
26
+ # - ruby: '3.0'
27
+ # os: 'macos-latest'
28
+ # experimental: true
29
29
 
30
30
  steps:
31
31
  - uses: actions/checkout@master
@@ -37,8 +37,8 @@ jobs:
37
37
  - uses: actions/cache@v1
38
38
  with:
39
39
  path: vendor/bundle
40
- key: bundle-${{ matrix.os }}-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}
41
- restore-keys: bundle-${{ matrix.os }}-${{ matrix.ruby }}
40
+ key: bundle-v2-${{ matrix.os }}-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}
41
+ restore-keys: bundle-v2-${{ matrix.os }}-${{ matrix.ruby }}
42
42
 
43
43
  - if: matrix.os == 'macos-latest'
44
44
  run: brew install lcdf-typetools
data/fontist.gemspec CHANGED
@@ -27,22 +27,18 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = ["fontist"]
28
28
  spec.test_files = `git ls-files -- {spec}/*`.split("\n")
29
29
 
30
- spec.add_runtime_dependency "arr-pm", "~> 0.0.1"
31
30
  spec.add_runtime_dependency "down", "~> 5.0"
32
- spec.add_runtime_dependency "libmspack", "~> 0.1.0"
33
- spec.add_runtime_dependency "rubyzip", "~> 2.3.0"
34
- spec.add_runtime_dependency "seven_zip_ruby", "~> 1.0"
35
- spec.add_runtime_dependency "ruby-ole", "~> 1.0"
36
31
  spec.add_runtime_dependency "thor", "~> 1.0.1"
37
32
  spec.add_runtime_dependency "git", "~> 1.0"
38
- spec.add_runtime_dependency "ttfunk", "~> 1.0"
33
+ spec.add_runtime_dependency "ttfunk", "~> 1.6.2"
34
+ spec.add_runtime_dependency "excavate", "~> 0.1"
39
35
 
40
36
  spec.add_development_dependency "extract_ttc", "~> 0.1"
41
37
  spec.add_development_dependency "pry"
42
38
  spec.add_development_dependency "bundler", "~> 2.0"
43
39
  spec.add_development_dependency "gem-release"
44
40
  spec.add_development_dependency "nokogiri", "~> 1.0"
45
- spec.add_development_dependency "rake", "~> 12.3.3"
41
+ spec.add_development_dependency "rake", "~> 13"
46
42
  spec.add_development_dependency "rspec", "~> 3.0"
47
43
  spec.add_development_dependency "rubocop", "0.75.0"
48
44
  spec.add_development_dependency "rubocop-rails"
data/lib/fontist/cli.rb CHANGED
@@ -29,12 +29,16 @@ module Fontist
29
29
  desc "install FONT", "Install font"
30
30
  option :force, type: :boolean, aliases: :f,
31
31
  desc: "Install even if it's already installed in system"
32
- option :confirm_license, type: :boolean, desc: "Confirm license agreement"
32
+ option :accept_all_licenses, type: :boolean, aliases: "--confirm-license", desc: "Accept all license agreements"
33
+ option :hide_licenses, type: :boolean, desc: "Hide license texts"
34
+ option :no_progress, type: :boolean, desc: "Hide download progress"
33
35
  def install(font)
34
36
  Fontist::Font.install(
35
37
  font,
36
38
  force: options[:force],
37
- confirmation: options[:confirm_license] ? "yes" : "no"
39
+ confirmation: options[:accept_all_licenses] ? "yes" : "no",
40
+ hide_licenses: options[:hide_licenses],
41
+ no_progress: options[:no_progress]
38
42
  )
39
43
  success
40
44
  rescue Fontist::Errors::GeneralError => e
@@ -89,11 +93,13 @@ module Fontist
89
93
  end
90
94
 
91
95
  desc "manifest-install MANIFEST", "Install fonts from MANIFEST (yaml)"
92
- option :confirm_license, type: :boolean, desc: "Confirm license agreement"
96
+ option :accept_all_licenses, type: :boolean, aliases: "--confirm-license", desc: "Accept all license agreements"
97
+ option :hide_licenses, type: :boolean, desc: "Hide license texts"
93
98
  def manifest_install(manifest)
94
99
  paths = Fontist::Manifest::Install.from_file(
95
100
  manifest,
96
- confirmation: options[:confirm_license] ? "yes" : "no"
101
+ confirmation: options[:accept_all_licenses] ? "yes" : "no",
102
+ hide_licenses: options[:hide_licenses]
97
103
  )
98
104
 
99
105
  print_yaml(paths)
data/lib/fontist/font.rb CHANGED
@@ -6,6 +6,8 @@ module Fontist
6
6
  def initialize(options = {})
7
7
  @name = options[:name]
8
8
  @confirmation = options[:confirmation] || "no"
9
+ @hide_licenses = options[:hide_licenses]
10
+ @no_progress = options[:no_progress] || false
9
11
  @force = options[:force] || false
10
12
 
11
13
  check_or_create_fontist_path!
@@ -19,8 +21,8 @@ module Fontist
19
21
  new(name: name).find
20
22
  end
21
23
 
22
- def self.install(name, confirmation: "no", force: false)
23
- new(name: name, confirmation: confirmation, force: force).install
24
+ def self.install(name, options = {})
25
+ new(options.merge(name: name)).install
24
26
  end
25
27
 
26
28
  def self.uninstall(name)
@@ -65,7 +67,7 @@ module Fontist
65
67
 
66
68
  private
67
69
 
68
- attr_reader :name, :confirmation
70
+ attr_reader :name
69
71
 
70
72
  def find_system_font
71
73
  paths = Fontist::SystemFont.find(name)
@@ -93,13 +95,17 @@ module Fontist
93
95
  end
94
96
 
95
97
  def font_installer(formula)
96
- FontInstaller.new(formula)
98
+ FontInstaller.new(formula, no_progress: @no_progress)
97
99
  end
98
100
 
99
101
  def formula
100
102
  @formula ||= Fontist::Formula.find(name)
101
103
  end
102
104
 
105
+ def formulas
106
+ @formulas ||= Fontist::Formula.find_many(name)
107
+ end
108
+
103
109
  def downloadable_font
104
110
  if formula
105
111
  raise Fontist::Errors::MissingFontError.new(name)
@@ -107,8 +113,10 @@ module Fontist
107
113
  end
108
114
 
109
115
  def download_font
110
- if formula
111
- check_and_confirm_required_license(formula)
116
+ return if formulas.empty?
117
+
118
+ formulas.flat_map do |formula|
119
+ confirmation = check_and_confirm_required_license(formula)
112
120
  paths = font_installer(formula).install(confirmation: confirmation)
113
121
 
114
122
  Fontist.ui.say("Fonts installed at:")
@@ -119,19 +127,24 @@ module Fontist
119
127
  end
120
128
 
121
129
  def check_and_confirm_required_license(formula)
122
- if formula.license_required && !confirmation.casecmp("yes").zero?
123
- @confirmation = show_license_and_ask_for_input(formula.license)
130
+ return @confirmation unless formula.license_required
124
131
 
125
- unless confirmation&.casecmp?("yes")
126
- raise Fontist::Errors::LicensingError.new(
127
- "Fontist will not download these fonts unless you accept the terms."
128
- )
129
- end
130
- end
132
+ show_license(formula.license) unless @hide_licenses
133
+ return @confirmation if @confirmation.casecmp?("yes")
134
+
135
+ confirmation = ask_for_agreement
136
+ return confirmation if confirmation&.casecmp?("yes")
137
+
138
+ raise Fontist::Errors::LicensingError.new(
139
+ "Fontist will not download these fonts unless you accept the terms."
140
+ )
131
141
  end
132
142
 
133
- def show_license_and_ask_for_input(license)
143
+ def show_license(license)
134
144
  Fontist.ui.say(license_agrement_message(license))
145
+ end
146
+
147
+ def ask_for_agreement
135
148
  Fontist.ui.ask(
136
149
  "\nDo you accept all presented font licenses, and want Fontist " \
137
150
  "to download these fonts for you? => TYPE 'Yes' or 'No':"
@@ -1,18 +1,11 @@
1
1
  require "fontist/utils"
2
+ require "excavate"
2
3
 
3
4
  module Fontist
4
5
  class FontInstaller
5
- include Utils::ZipExtractor
6
- include Utils::ExeExtractor
7
- include Utils::MsiExtractor
8
- include Utils::SevenZipExtractor
9
- include Utils::RpmExtractor
10
- include Utils::GzipExtractor
11
- include Utils::CpioExtractor
12
- include Utils::TarExtractor
13
-
14
- def initialize(formula)
6
+ def initialize(formula, no_progress: false)
15
7
  @formula = formula
8
+ @no_progress = no_progress
16
9
  end
17
10
 
18
11
  def install(confirmation:)
@@ -20,7 +13,6 @@ module Fontist
20
13
  raise(Fontist::Errors::LicensingError)
21
14
  end
22
15
 
23
- reinitialize
24
16
  install_font
25
17
  end
26
18
 
@@ -28,10 +20,6 @@ module Fontist
28
20
 
29
21
  attr_reader :formula
30
22
 
31
- def reinitialize
32
- @downloaded = false
33
- end
34
-
35
23
  def install_font
36
24
  fonts_paths = run_in_temp_dir { extract }
37
25
  fonts_paths.empty? ? nil : fonts_paths
@@ -50,47 +38,39 @@ module Fontist
50
38
  end
51
39
 
52
40
  def extract
53
- resource = @formula.resources.first
54
-
55
- [@formula.extract].flatten.each do |operation|
56
- resource = extract_by_operation(operation, resource)
57
- end
58
-
59
- fonts_paths = resource
41
+ archive = download_file(@formula.resources.first)
60
42
 
61
- fonts_paths
43
+ install_fonts_from_archive(archive)
62
44
  end
63
45
 
64
- def extract_by_operation(operation, resource)
65
- method = "#{operation.format}_extract"
66
- if operation.options
67
- send(method, resource, **operation.options.to_h)
68
- else
69
- send(method, resource)
70
- end
71
- end
46
+ def install_fonts_from_archive(archive)
47
+ Fontist.ui.say(%(Installing font "#{@formula.key}".))
72
48
 
73
- def fonts_path
74
- Fontist.fonts_path
49
+ Array.new.tap do |fonts_paths|
50
+ Excavate::Archive.new(archive.path).files(recursive_packages: true) do |path|
51
+ fonts_paths << install_font_file(path) if font_file?(path)
52
+ end
53
+ end
75
54
  end
76
55
 
77
56
  def download_file(source)
78
57
  url = source.urls.first
79
58
  Fontist.ui.say(%(Downloading font "#{@formula.key}" from #{url}))
80
59
 
81
- downloaded_file = Fontist::Utils::Downloader.download(
60
+ Fontist::Utils::Downloader.download(
82
61
  url,
83
62
  sha: source.sha256,
84
63
  file_size: source.file_size,
85
- progress_bar: true
64
+ progress_bar: !@no_progress
86
65
  )
66
+ end
87
67
 
88
- @downloaded = true
89
- downloaded_file
68
+ def font_file?(path)
69
+ source_file?(path) && font_directory?(path)
90
70
  end
91
71
 
92
- def font_file?(filename)
93
- source_files.include?(filename)
72
+ def source_file?(path)
73
+ source_files.include?(File.basename(path))
94
74
  end
95
75
 
96
76
  def source_files
@@ -101,6 +81,27 @@ module Fontist
101
81
  end
102
82
  end
103
83
 
84
+ def font_directory?(path)
85
+ return true unless subdirectory_pattern
86
+
87
+ File.fnmatch?(subdirectory_pattern, File.dirname(path))
88
+ end
89
+
90
+ def subdirectory_pattern
91
+ @subdirectory_pattern ||= "*" + subdirectories.first.chomp("/") unless subdirectories.empty?
92
+ end
93
+
94
+ def subdirectories
95
+ @subdirectories ||= [@formula.extract].flatten.map(&:options).compact.map(&:fonts_sub_dir).compact
96
+ end
97
+
98
+ def install_font_file(source)
99
+ target = Fontist.fonts_path.join(target_filename(File.basename(source))).to_s
100
+ FileUtils.mv(source, target)
101
+
102
+ target
103
+ end
104
+
104
105
  def target_filename(source_filename)
105
106
  target_filenames[source_filename]
106
107
  end
@@ -24,6 +24,10 @@ module Fontist
24
24
  Indexes::FontIndex.from_yaml.load_formulas(font_name).first
25
25
  end
26
26
 
27
+ def self.find_many(font_name)
28
+ Indexes::FontIndex.from_yaml.load_formulas(font_name)
29
+ end
30
+
27
31
  def self.find_fonts(font_name)
28
32
  formulas = Indexes::FontIndex.from_yaml.load_formulas(font_name)
29
33
 
@@ -4,9 +4,7 @@ module Fontist
4
4
  module SystemHelper
5
5
  class << self
6
6
  def run(command)
7
- unless ENV.fetch("TEST_ENV", "") === "CI"
8
- Fontist.ui.say("Run `#{command}`")
9
- end
7
+ Fontist.ui.say("Run `#{command}`")
10
8
 
11
9
  result = `#{command}`
12
10
  unless $CHILD_STATUS.to_i.zero?
@@ -1,21 +1,18 @@
1
- require "find"
2
- require_relative "extractors"
3
1
  require_relative "files/font_detector"
4
2
 
5
3
  module Fontist
6
4
  module Import
7
5
  class RecursiveExtraction
8
- FONTS_PATTERN = "**/*.{ttf,otf,ttc}".freeze
9
- ARCHIVE_EXTENSIONS = %w[zip msi exe cab].freeze
10
6
  LICENSE_PATTERN = /(ofl\.txt|ufl\.txt|licenses?\.txt|copying)$/i.freeze
11
7
 
12
8
  def initialize(archive, subarchive: nil, subdir: nil)
13
9
  @archive = archive
14
- @subarchive = subarchive
15
10
  @subdir = subdir
16
- @operations = []
11
+ @operations = {}
17
12
  @font_files = []
18
13
  @collection_files = []
14
+
15
+ save_operation_subdir
19
16
  end
20
17
 
21
18
  def extension
@@ -39,11 +36,18 @@ module Fontist
39
36
 
40
37
  def operations
41
38
  ensure_extracted
42
- @operations.size == 1 ? @operations.first : @operations
39
+ @operations
43
40
  end
44
41
 
45
42
  private
46
43
 
44
+ def save_operation_subdir
45
+ return unless @subdir
46
+
47
+ @operations[:options] ||= {}
48
+ @operations[:options][:fonts_sub_dir] = @subdir
49
+ end
50
+
47
51
  def fetch_extension(file)
48
52
  File.extname(filename(file)).sub(/^\./, "")
49
53
  end
@@ -57,68 +61,21 @@ module Fontist
57
61
  end
58
62
 
59
63
  def ensure_extracted
60
- extracted_path
61
- end
64
+ return if @extracted
62
65
 
63
- def extracted_path
64
- @extracted_path ||= extract_recursively(@archive)
66
+ extract_data(@archive)
67
+ @extracted = true
65
68
  end
66
69
 
67
- def extract_recursively(archive)
68
- path = operate_on_archive(archive)
69
- match_files(path)
70
- if matched?
71
- save_operation_subdir
72
- return path
73
- end
74
-
75
- next_archive = find_archive(path)
76
- extract_recursively(next_archive)
77
- end
78
-
79
- def operate_on_archive(archive)
80
- extractor = choose_extractor(archive)
81
- Fontist.ui.say("Extracting #{archive} with #{extractor.class.name}")
82
-
83
- save_operation(extractor)
84
- extractor.extract
85
- end
86
-
87
- # rubocop:disable Metrics/MethodLength
88
- def choose_extractor(archive)
89
- case fetch_extension(archive).downcase
90
- when "msi"
91
- Extractors::OleExtractor.new(archive)
92
- when "cab"
93
- Extractors::CabExtractor.new(archive)
94
- when "exe"
95
- extractor = Extractors::SevenZipExtractor.new(archive)
96
- extractor.try ? extractor : Extractors::CabExtractor.new(archive)
97
- when "zip"
98
- Extractors::ZipExtractor.new(archive)
99
- when "rpm"
100
- Extractors::RpmExtractor.new(archive)
101
- when "gz"
102
- Extractors::GzipExtractor.new(archive)
103
- when "cpio"
104
- Extractors::CpioExtractor.new(archive)
105
- when "tar"
106
- Extractors::TarExtractor.new(archive)
107
- else
108
- raise Errors::UnknownArchiveError, "Could not unarchive `#{filename(archive)}`."
70
+ def extract_data(archive)
71
+ Excavate::Archive.new(path(archive)).files(recursive_packages: true) do |path|
72
+ match_license(path)
73
+ match_font(path) if font_directory?(path)
109
74
  end
110
75
  end
111
- # rubocop:enable Metrics/MethodLength
112
76
 
113
- def save_operation(extractor)
114
- @operations << { format: extractor.format }
115
- end
116
-
117
- def match_files(dir_path)
118
- Find.find(dir_path) do |entry_path| # rubocop:disable Style/CollectionMethods
119
- match_license(entry_path)
120
- match_font(entry_path) if font_directory?(entry_path, dir_path)
121
- end
77
+ def path(file)
78
+ file.respond_to?(:path) ? file.path : file
122
79
  end
123
80
 
124
81
  def match_license(path)
@@ -129,18 +86,6 @@ module Fontist
129
86
  file.match?(LICENSE_PATTERN)
130
87
  end
131
88
 
132
- def font_directory?(path, base_path)
133
- return true unless @subdir
134
-
135
- # https://bugs.ruby-lang.org/issues/10011
136
- base_path = Pathname.new(base_path)
137
-
138
- relative_path = Pathname.new(path).relative_path_from(base_path).to_s
139
- dirname = File.dirname(relative_path)
140
- normalized_pattern = @subdir.chomp("/")
141
- File.fnmatch?(normalized_pattern, dirname)
142
- end
143
-
144
89
  def match_font(path)
145
90
  case Files::FontDetector.detect(path)
146
91
  when :font
@@ -150,53 +95,14 @@ module Fontist
150
95
  end
151
96
  end
152
97
 
153
- def matched?
154
- [@font_files, @collection_files].any? do |files|
155
- files.size.positive?
156
- end
157
- end
158
-
159
- def save_operation_subdir
160
- return unless @subdir
161
-
162
- @operations.last[:options] ||= {}
163
- @operations.last[:options][:fonts_sub_dir] = @subdir
164
- end
165
-
166
- def find_archive(path)
167
- children = Dir.entries(path) - [".", ".."] # ruby 2.4 compat
168
- paths = children.map { |file| File.join(path, file) }
169
- by_subarchive(paths) || by_size(paths)
170
- end
171
-
172
- def by_subarchive(paths)
173
- return unless @subarchive
174
-
175
- path_found = paths.detect do |path|
176
- @subarchive == File.basename(path)
177
- end
178
-
179
- return unless path_found
180
-
181
- save_operation_subarchive(path_found)
182
-
183
- path_found
184
- end
185
-
186
- def save_operation_subarchive(path)
187
- @operations.last[:options] ||= {}
188
- @operations.last[:options][:subarchive] = File.basename(path)
189
- end
98
+ def font_directory?(path)
99
+ return true unless subdirectory_pattern
190
100
 
191
- def by_size(paths)
192
- paths.max_by do |path|
193
- [file_type(path), File.size(path)]
194
- end
101
+ File.fnmatch?(subdirectory_pattern, File.dirname(path))
195
102
  end
196
103
 
197
- def file_type(file_path)
198
- extension = File.extname(file_path).delete(".")
199
- ARCHIVE_EXTENSIONS.include?(extension) ? 1 : 0
104
+ def subdirectory_pattern
105
+ @subdirectory_pattern ||= "*" + @subdir.chomp("/") if @subdir
200
106
  end
201
107
  end
202
108
  end