tetra 0.49.0 → 0.50.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.rubocop.yml +2 -2
  2. data/SPECIAL_CASES.md +20 -8
  3. data/integration-tests/build-commons.sh +5 -10
  4. data/lib/template/kit.spec +1 -1
  5. data/lib/template/package.spec +7 -1
  6. data/lib/tetra.rb +1 -2
  7. data/lib/tetra/facades/git.rb +39 -18
  8. data/lib/tetra/facades/process_runner.rb +13 -4
  9. data/lib/tetra/main.rb +6 -0
  10. data/lib/tetra/packages/kit_package.rb +0 -6
  11. data/lib/tetra/packages/package.rb +4 -6
  12. data/lib/tetra/pom.rb +2 -5
  13. data/lib/tetra/project.rb +85 -30
  14. data/lib/tetra/ui/ant_subcommand.rb +1 -1
  15. data/lib/tetra/ui/dry_run_subcommand.rb +36 -22
  16. data/lib/tetra/ui/generate_all_subcommand.rb +1 -1
  17. data/lib/tetra/ui/generate_archive_subcommand.rb +2 -2
  18. data/lib/tetra/ui/generate_kit_subcommand.rb +2 -2
  19. data/lib/tetra/ui/generate_script_subcommand.rb +1 -1
  20. data/lib/tetra/ui/generate_spec_subcommand.rb +10 -3
  21. data/lib/tetra/ui/get_pom_subcommand.rb +18 -16
  22. data/lib/tetra/ui/move_jars_to_kit_subcommand.rb +1 -1
  23. data/lib/tetra/ui/mvn_subcommand.rb +1 -1
  24. data/lib/tetra/ui/patch_subcommand.rb +18 -0
  25. data/lib/tetra/ui/subcommand.rb +14 -5
  26. data/lib/tetra/version.rb +1 -1
  27. data/spec/lib/git_spec.rb +69 -30
  28. data/spec/lib/kit_package_spec.rb +0 -16
  29. data/spec/lib/{built_package_spec.rb → package_spec.rb} +2 -15
  30. data/spec/lib/project_spec.rb +56 -13
  31. data/spec/lib/{speccable.rb → speccable_spec.rb} +5 -5
  32. metadata +5 -9
  33. data/integration-tests/apache-maven-3.1.1-bin.zip +0 -0
  34. data/integration-tests/build-obs.sh +0 -19
  35. data/lib/tetra/facades/tar.rb +0 -20
  36. data/lib/tetra/packages/archivable.rb +0 -18
  37. data/spec/lib/archivable_spec.rb +0 -36
data/.rubocop.yml CHANGED
@@ -11,10 +11,10 @@ AbcSize:
11
11
  Max: 50
12
12
 
13
13
  CyclomaticComplexity:
14
- Max: 10
14
+ Max: 20
15
15
 
16
16
  PerceivedComplexity:
17
- Max: 15
17
+ Max: 20
18
18
 
19
19
  StringLiterals:
20
20
  EnforcedStyle: double_quotes
data/SPECIAL_CASES.md CHANGED
@@ -4,32 +4,44 @@
4
4
 
5
5
  If your build fails for whatever reason, abort it with `tetra finish abort`. `tetra` will restore all project files as they were before build.
6
6
 
7
- ## Manual changes to generated files
7
+ ## Generating files multiple times
8
8
 
9
- You can do any manual changes to spec and build.sh files and regenerate them later, `tetra` will reconcile changes with a [three-way merge](http://en.wikipedia.org/wiki/Three-way_merge#Three-way_merge) and alert about any conflicts. You can generate single files with the following commands:
9
+ You can edit any file generated by tetra - regenerating it later will not overwrite your changes.
10
+
11
+ `tetra` will try to reconcile your edits with a [three-way merge](http://en.wikipedia.org/wiki/Three-way_merge#Three-way_merge) - if that fails you will be warned about any conflicts.
12
+
13
+ You can generate single files with the following commands:
10
14
 
11
15
  * `tetra generate-script`: (re)generates the `build.sh` file from the latest bash history (assumes `tetra dry-run start` and `tetra dry-run finish` have been used);
12
16
  * `tetra generate-archive`: (re)generates the package tarball;
13
17
  * `tetra generate-spec`: (re)generates the package spec;
14
18
  * `tetra generate-kit`: (re)generates the kit tarball and spec;
15
19
 
20
+ ## Patches
21
+
22
+ If you need to modify your project's sources for whatever reason, feel free to do so and then use `tetra patch "my patch message"` to signal your changes.
23
+
24
+ After that you can retry the dry-run and spec generation: tetra will take care of creating patch files and adding `%patch` instructions for you.
25
+
26
+ In case you want to swap sources completely and throw away all previous patches use `tetra patch --new-tarball`.
27
+
16
28
  ## Ant builds
17
29
 
18
- `tetra` is currently optimized for Maven as it is the most common build tool, but it can work with any other. In particular, support for Ant has already been implemented and `tetra ant` works like `tetra mvn`, and a copy of ant is also bundled in `kit/` by default.
30
+ `tetra` works best with Maven but supports Ant as well. `tetra ant` works like `tetra mvn`, and a copy of ant is also bundled in `kit/` by default.
19
31
 
20
- Sometimes you will have jar files distributed along with the source archive that will end up in `src/`: you don't want that! Run
32
+ Sometimes you will have jar files distributed along with the source archive that will end up in `src/`: you don't want that! Run:
21
33
 
22
34
  tetra move-jars-to-kit
23
35
 
24
- to have them moved to `kit/jars`. The command will generate a symlink back to the original, so builds will work as expected.
36
+ to have them moved to `kit/jars`. The command will generate symlinks back to the originals, so builds will work as expected.
25
37
 
26
- When generating spec files, be sure to have a `pom.xml` in your package directory even if you are not using Maven: `tetra` will automatically take advantage of information from it to compile many fields.
38
+ When generating spec files, it helps to have a `pom.xml` in your package directory even if you are not using Maven, as `tetra` will automatically take advantage of information from it to compile many fields, but it's not required.
27
39
 
28
- You can also ask `tetra` to find one via `tetra get-pom <filename>.jar` (be sure to have Maven in your kit).
40
+ You can also ask `tetra` to find one via `tetra get-pom <filename>.jar`.
29
41
 
30
42
  ## Other build tools
31
43
 
32
- Other build tools are currently unsupported but will be added in the future. You can nevertheless use them - the only rule is that you have to keep all of their files in `kit`.
44
+ Other build tools are currently unsupported but will be added in the future. You can nevertheless use them just make sure all of their files, included automatically downloaded ones, are stored in `kit`.
33
45
 
34
46
  ## [OBS](build.opensuse.org) integration
35
47
 
@@ -20,16 +20,11 @@ tetra dry-run finish
20
20
  tetra generate-kit
21
21
  tetra generate-archive
22
22
  tetra generate-spec
23
- # simulate tetra generate-script
24
- cd ../..
25
- cat >packages/commons-collections/build.sh <<"EOF"
26
- #!/bin/bash
27
- PROJECT_PREFIX=`readlink -e .`
28
- cd .
29
- cd src/commons-collections-3.2.1-src/
30
- $PROJECT_PREFIX/kit/apache-maven-3.2.5/bin/mvn -Dmaven.repo.local=$PROJECT_PREFIX/kit/m2 -s$PROJECT_PREFIX/kit/m2/settings.xml -o package -DskipTests
31
- EOF
23
+
24
+ touch adding_patch_file
25
+ tetra patch "patch file added"
26
+ tetra generate-spec
32
27
 
33
28
  echo "**************** All Done ****************"
34
29
 
35
- ls -lah *
30
+ find ../../packages
@@ -33,7 +33,7 @@ It should not be used except for rebuilding other packages,
33
33
  thus it should never be installed on end users' systems.
34
34
 
35
35
  %prep
36
- %setup -q -c
36
+ %setup -q -n kit
37
37
 
38
38
  %build
39
39
  # nothing to do, precompiled by design
@@ -24,6 +24,9 @@ Url: <%= url %>
24
24
  Group: Development/Libraries/Java
25
25
  Source0: %{name}.tar.xz
26
26
  Source1: build.sh
27
+ <% patches.to_enum.with_index.each do |patch, i| %>
28
+ Patch<%= i %>: <%= patch %>
29
+ <% end %>
27
30
  BuildRoot: %{_tmppath}/%{name}-%{version}-build
28
31
  BuildRequires: xz
29
32
  BuildRequires: java-devel
@@ -41,7 +44,10 @@ Requires: mvn(<%= dependency_id[0] %>:<%= dependency_id[1] %>) <% if depen
41
44
  %>
42
45
 
43
46
  %prep
44
- %setup -q -c -n src
47
+ %setup -q -n src
48
+ <% (0..(patches.count -1)).each do |i| %>
49
+ %patch<%= i %> -p2
50
+ <% end %>
45
51
  cp -f %{SOURCE1} .
46
52
  cp -Rf %{_datadir}/tetra ../kit
47
53
 
data/lib/tetra.rb CHANGED
@@ -26,7 +26,6 @@ require "tetra/logger"
26
26
  # facades to other programs
27
27
  require "tetra/facades/process_runner"
28
28
  require "tetra/facades/git"
29
- require "tetra/facades/tar"
30
29
  require "tetra/facades/ant"
31
30
  require "tetra/facades/mvn"
32
31
 
@@ -40,7 +39,6 @@ require "tetra/maven_website"
40
39
  require "tetra/pom_getter"
41
40
 
42
41
  # package building
43
- require "tetra/packages/archivable"
44
42
  require "tetra/packages/speccable"
45
43
  require "tetra/packages/scriptable"
46
44
  require "tetra/packages/kit_package"
@@ -59,5 +57,6 @@ require "tetra/ui/get_pom_subcommand"
59
57
  require "tetra/ui/init_subcommand"
60
58
  require "tetra/ui/move_jars_to_kit_subcommand"
61
59
  require "tetra/ui/mvn_subcommand"
60
+ require "tetra/ui/patch_subcommand"
62
61
 
63
62
  require "tetra/main"
@@ -43,29 +43,16 @@ module Tetra
43
43
  end
44
44
  end
45
45
 
46
- # returns a list of filenames that changed in the repo
47
- # between specified ids, in a certain directory
48
- def changed_files_between(start_id, end_id, directory)
49
- Dir.chdir(@directory) do
50
- run("git diff-tree \
51
- --no-commit-id \
52
- --name-only \
53
- -r #{start_id} #{end_id}\
54
- -- #{directory}"
55
- ).split("\n")
56
- end
57
- end
58
-
59
46
  # adds all files in the current directory,
60
47
  # removes all files not in the current directory,
61
48
  # commits with message
62
- def commit_whole_directory(message)
49
+ def commit_whole_directory(directory, message)
63
50
  Dir.chdir(@directory) do
64
51
  log.debug "committing with message: #{message}"
65
52
 
66
- run("git rm -r --cached --ignore-unmatch .")
67
- run("git add .")
68
- run("git commit --allow-empty -m \"#{message}\"")
53
+ run("git rm -r --cached --ignore-unmatch #{directory}")
54
+ run("git add #{directory}")
55
+ run("git commit --allow-empty -F -", false, message)
69
56
  end
70
57
  end
71
58
 
@@ -74,7 +61,7 @@ module Tetra
74
61
  Dir.chdir(@directory) do
75
62
  log.debug "committing path #{path} with message: #{message}"
76
63
  run("git add #{path}")
77
- run("git commit --allow-empty -m \"#{message}\"")
64
+ run("git commit --allow-empty -F -", false, message)
78
65
  end
79
66
  end
80
67
 
@@ -128,6 +115,40 @@ module Tetra
128
115
  conflict_count
129
116
  end
130
117
  end
118
+
119
+ # returns the list of files changed from since_id
120
+ # including changes in the working tree and staging
121
+ # area
122
+ def changed_files(directory, id)
123
+ Dir.chdir(@directory) do
124
+ tracked_files = []
125
+ begin
126
+ tracked_files += run("git diff-index --name-only #{id} -- #{directory}").split
127
+ rescue ExecutionFailed => e
128
+ raise e if e.status != 1 # status 1 is normal
129
+ end
130
+
131
+ untracked_files = run("git ls-files --exclude-standard --others -- #{directory}").split
132
+ tracked_files + untracked_files
133
+ end
134
+ end
135
+
136
+ # archives version id of directory in destination_path
137
+ def archive(directory, id, destination_path)
138
+ Dir.chdir(@directory) do
139
+ FileUtils.mkdir_p(File.dirname(destination_path))
140
+ run("git archive --format=tar #{id} -- #{directory} | xz -9e > #{destination_path}")
141
+ end
142
+ destination_path
143
+ end
144
+
145
+ # generates patch files to changes to directory in destination_path
146
+ # since from_id
147
+ def format_patch(directory, from_id, destination_path)
148
+ Dir.chdir(@directory) do
149
+ run("git format-patch -o #{destination_path} --numbered #{from_id} -- #{directory}").split
150
+ end
151
+ end
131
152
  end
132
153
 
133
154
  class GitAlreadyInitedError < StandardError
@@ -8,13 +8,14 @@ module Tetra
8
8
  # runs an external executable and returns its output as a string
9
9
  # raises ExecutionFailed if the exit status is not 0
10
10
  # optionally echoes the executable's output/error to standard output/error
11
- def run(commandline, echo = false)
11
+ def run(commandline, echo = false, stdin = nil)
12
12
  log.debug "running `#{commandline}`"
13
13
 
14
14
  out_recorder = echo ? RecordingIO.new(STDOUT) : RecordingIO.new
15
15
  err_recorder = echo ? RecordingIO.new(STDERR) : RecordingIO.new
16
16
 
17
- status = Open4.spawn(commandline, stdout: out_recorder, stderr: err_recorder, quiet: true).exitstatus
17
+ status = Open4.spawn(commandline, stdin: stdin, stdout: out_recorder,
18
+ stderr: err_recorder, quiet: true).exitstatus
18
19
 
19
20
  log.debug "`#{commandline}` exited with status #{status}"
20
21
 
@@ -27,7 +28,7 @@ module Tetra
27
28
  log.warn(out) unless out == ""
28
29
  log.warn(err) unless err == ""
29
30
  end
30
- fail ExecutionFailed.new(commandline, status)
31
+ fail ExecutionFailed.new(commandline, status, out, err)
31
32
  end
32
33
 
33
34
  out_recorder.record
@@ -57,10 +58,18 @@ module Tetra
57
58
  class ExecutionFailed < Exception
58
59
  attr_reader :commandline
59
60
  attr_reader :status
61
+ attr_reader :out
62
+ attr_reader :err
60
63
 
61
- def initialize(commandline, status)
64
+ def initialize(commandline, status, out, err)
62
65
  @commandline = commandline
63
66
  @status = status
67
+ @out = out
68
+ @err = err
69
+ end
70
+
71
+ def to_s
72
+ "\"#{@commandline}\" failed with status #{@status}\n#{out}\n#{err}"
64
73
  end
65
74
  end
66
75
  end
data/lib/tetra/main.rb CHANGED
@@ -57,6 +57,12 @@ module Tetra
57
57
  Tetra::GenerateAllSubcommand
58
58
  )
59
59
 
60
+ subcommand(
61
+ "patch",
62
+ "Saves changes in source files for inclusion in a patch",
63
+ Tetra::PatchSubcommand
64
+ )
65
+
60
66
  subcommand(
61
67
  "move-jars-to-kit",
62
68
  "Locates jars in src/ and moves them to kit/",
@@ -4,7 +4,6 @@ module Tetra
4
4
  # a packaged set of binary build-time dependencies
5
5
  class KitPackage
6
6
  extend Forwardable
7
- include Archivable
8
7
  include Speccable
9
8
 
10
9
  attr_reader :name
@@ -17,11 +16,6 @@ module Tetra
17
16
  @name = "#{project.name}-kit"
18
17
  end
19
18
 
20
- def to_archive
21
- _to_archive(@project, name, "kit",
22
- @project.packages_dir)
23
- end
24
-
25
19
  def to_spec
26
20
  _to_spec(@project, name, "kit.spec",
27
21
  @project.packages_dir)
@@ -4,10 +4,11 @@ module Tetra
4
4
  # represents a Java project packaged in tetra
5
5
  class Package
6
6
  extend Forwardable
7
- include Archivable
8
7
  include Speccable
9
8
  include Scriptable
10
9
 
10
+ attr_reader :patches
11
+
11
12
  def_delegator :@project, :name, :name
12
13
  def_delegator :@kit, :name, :kit_name
13
14
  def_delegator :@kit, :version, :kit_version
@@ -18,11 +19,12 @@ module Tetra
18
19
  def_delegator :@pom, :version
19
20
  def_delegator :@pom, :runtime_dependency_ids
20
21
 
21
- def initialize(project, pom_path = nil, filter = nil)
22
+ def initialize(project, pom_path = nil, filter = nil, patches = [])
22
23
  @project = project
23
24
  @kit = Tetra::KitPackage.new(project)
24
25
  @pom = Tetra::Pom.new(pom_path)
25
26
  @filter = filter
27
+ @patches = patches.map { |f| File.basename(f) }
26
28
  end
27
29
 
28
30
  # a short summary from the POM
@@ -51,10 +53,6 @@ module Tetra
51
53
  .sub(/\.+$/, "")
52
54
  end
53
55
 
54
- def to_archive
55
- _to_archive(@project, name, "src", @project.packages_dir)
56
- end
57
-
58
56
  def to_spec
59
57
  _to_spec(@project, name, "package.spec", @project.packages_dir)
60
58
  end
data/lib/tetra/pom.rb CHANGED
@@ -4,11 +4,8 @@ module Tetra
4
4
  # encapsulates a pom.xml file
5
5
  class Pom
6
6
  def initialize(filename)
7
- @doc = Nokogiri::XML(
8
- if filename && File.file?(filename)
9
- open(filename).read
10
- end
11
- )
7
+ content = open(filename).read if filename && File.file?(filename)
8
+ @doc = Nokogiri::XML(content)
12
9
  @doc.remove_namespaces!
13
10
  end
14
11
 
data/lib/tetra/project.rb CHANGED
@@ -61,7 +61,7 @@ module Tetra
61
61
  FileUtils.cp_r(File.join(TEMPLATE_PATH, source), destination)
62
62
  end
63
63
 
64
- project.commit_whole_project("Template files added")
64
+ project.commit_whole_directory(".", "Template files added")
65
65
  end
66
66
  end
67
67
 
@@ -86,16 +86,26 @@ module Tetra
86
86
  result
87
87
  end
88
88
 
89
+ # checks whether there were edits to src/
90
+ # since last mark
91
+ def src_patched?
92
+ from_directory do
93
+ latest_id = @git.latest_id("tetra: sources-")
94
+ if latest_id
95
+ @git.changed_files("src", latest_id).any?
96
+ else
97
+ false
98
+ end
99
+ end
100
+ end
101
+
89
102
  # starts a dry running phase: files added to kit/ will be added
90
103
  # to the kit package, src/ will be reset at the current state
91
104
  # when finished
92
105
  def dry_run
93
- return false if dry_running?
94
-
95
106
  current_directory = Pathname.new(Dir.pwd).relative_path_from(Pathname.new(@full_path))
96
107
 
97
- commit_whole_project("Dry-run started", "tetra: dry-run-started: #{current_directory}")
98
- true
108
+ commit_whole_directory(".", "Dry-run started\n", "tetra: dry-run-started: #{current_directory}")
99
109
  end
100
110
 
101
111
  # returns true iff we are currently dry-running
@@ -107,30 +117,35 @@ module Tetra
107
117
  # ends a dry-run assuming a successful build
108
118
  # reverts sources and updates output file lists
109
119
  def finish
110
- if dry_running?
111
- commit_whole_project("Changes during dry-run", "tetra: dry-run-changed")
120
+ # keep track of changed files
121
+ start_id = @git.latest_id("tetra: dry-run-started")
122
+ changed_files = @git.changed_files("src", start_id)
112
123
 
113
- @git.revert_whole_directory("src", @git.latest_id("tetra: dry-run-started"))
124
+ # revert to pre-dry-run status
125
+ @git.revert_whole_directory("src", start_id)
114
126
 
115
- commit_whole_project("Dry run finished", "tetra: dry-run-finished")
116
- return true
127
+ # prepare commit comments
128
+ comments = ["Dry run finished\n", "tetra: dry-run-finished"]
129
+ comments += changed_files.map { |f| "tetra: file-changed: #{f}" }
130
+
131
+ # if this is the first dry-run, mark sources as tarball
132
+ if @git.latest_id("tetra: dry-run-finished").nil?
133
+ comments << "tetra: sources-tarball"
117
134
  end
118
- false
135
+
136
+ # commit end of dry run
137
+ commit_whole_directory(".", *comments)
119
138
  end
120
139
 
121
- # ends a dry-run assuming the built went wrong
140
+ # ends a dry-run assuming the build went wrong
122
141
  # reverts the whole project directory
123
142
  def abort
124
- if dry_running?
125
- @git.revert_whole_directory(".", @git.latest_id("tetra: dry-run-started"))
126
- @git.undo_last_commit
127
- return true
128
- end
129
- false
143
+ @git.revert_whole_directory(".", @git.latest_id("tetra: dry-run-started"))
144
+ @git.undo_last_commit
130
145
  end
131
146
 
132
- # commits all files in the project
133
- def commit_whole_project(*comments)
147
+ # commits all files in the directory
148
+ def commit_whole_directory(directory, *comments)
134
149
  # rename all .gitignore files that might have slipped in
135
150
  from_directory("src") do
136
151
  Find.find(".") do |file|
@@ -140,7 +155,16 @@ module Tetra
140
155
  end
141
156
  end
142
157
 
143
- @git.commit_whole_directory(comments.join("\n\n"))
158
+ @git.commit_whole_directory(directory, comments.join("\n"))
159
+ end
160
+
161
+ # commits files in the src/ dir as a patch or tarball update
162
+ def commit_sources(message, new_tarball = false)
163
+ from_directory do
164
+ comments = ["#{message}\n"]
165
+ comments << "tetra: sources-tarball" if new_tarball
166
+ commit_whole_directory("src", comments)
167
+ end
144
168
  end
145
169
 
146
170
  # replaces content in path with new_content, commits using
@@ -172,8 +196,11 @@ module Tetra
172
196
 
173
197
  if already_existing
174
198
  # 3-way merge
175
- conflict_count = @git.merge_with_id("#{path}", "#{path}.tetra_user_edited", previous_id)
199
+ conflict_count = @git.merge_with_id(path, "#{path}.tetra_user_edited", previous_id)
176
200
  File.delete("#{path}.tetra_user_edited")
201
+
202
+ @git.commit_file(path, "User changes merged back") if conflict_count == 0
203
+
177
204
  return conflict_count
178
205
  end
179
206
  return 0
@@ -194,14 +221,42 @@ module Tetra
194
221
 
195
222
  # returns a list of files produced during the last dry-run
196
223
  def produced_files
197
- start_id = @git.latest_id("tetra: dry-run-started")
198
- end_id = @git.latest_id("tetra: dry-run-changed")
199
- if !start_id.nil? && !end_id.nil?
200
- @git.changed_files_between(start_id, end_id, "src")
201
- .sort
202
- .map { |file| Pathname.new(file).relative_path_from(Pathname.new("src")).to_s }
203
- else
204
- []
224
+ @git.latest_comment("tetra: dry-run-finished")
225
+ .split("\n")
226
+ .map { |line| line[/^tetra: file-changed: src\/(.+)$/, 1] }
227
+ .compact
228
+ .sort
229
+ end
230
+
231
+ # archives a tarball of src/ in packages/
232
+ # the latest commit marked as tarball is taken as the version
233
+ def archive_sources
234
+ from_directory do
235
+ id = @git.latest_id("tetra: sources-tarball")
236
+ destination_path = File.join(full_path, packages_dir, name, "#{name}.tar.xz")
237
+ @git.archive("src", id, destination_path)
238
+ end
239
+ end
240
+
241
+ # archives a tarball of kit/ in packages/
242
+ # the latest commit marked as dry-run-finished is taken as the version
243
+ def archive_kit
244
+ from_directory do
245
+ id = @git.latest_id("tetra: dry-run-finished")
246
+ destination_path = File.join(full_path, packages_dir, "#{name}-kit", "#{name}-kit.tar.xz")
247
+ @git.archive("kit", id, destination_path)
248
+ end
249
+ end
250
+
251
+ # generates patches of src/ in packages/
252
+ # the latest commit marked as tarball is taken as the base version,
253
+ # other commits are assumed to be patches on top
254
+ # returns filenames
255
+ def write_source_patches
256
+ from_directory do
257
+ id = @git.latest_id("tetra: sources-tarball")
258
+ destination_path = File.join(full_path, packages_dir, name)
259
+ @git.format_patch("src", id, destination_path)
205
260
  end
206
261
  end
207
262