runfile 0.12.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +108 -37
  3. data/bin/runn +5 -0
  4. data/examples/default-action/runfile +9 -0
  5. data/examples/default-action-2/runfile +11 -0
  6. data/examples/default-action-2/server.runfile +6 -0
  7. data/examples/different-name/runfile.rb +8 -0
  8. data/examples/example/runfile +10 -0
  9. data/examples/example-multiline/runfile +16 -0
  10. data/examples/execute/runfile +14 -0
  11. data/examples/execute/server.runfile +11 -0
  12. data/examples/full/runfile +24 -0
  13. data/examples/import/more_tasks/spec.runfile +7 -0
  14. data/examples/import/runfile +10 -0
  15. data/examples/import/tasks/server.runfile +13 -0
  16. data/examples/minimal/runfile +4 -0
  17. data/examples/multiple-runfiles/runfile +11 -0
  18. data/examples/multiple-runfiles/server.runfile +13 -0
  19. data/examples/named-only/deploy.runfile +7 -0
  20. data/examples/named-only/server.runfile +11 -0
  21. data/examples/naval-fate/runfile +47 -0
  22. data/examples/shortcut/runfile +31 -0
  23. data/lib/runfile/action.rb +40 -14
  24. data/lib/runfile/concerns/dsl.rb +132 -0
  25. data/lib/runfile/concerns/inspectable.rb +13 -0
  26. data/lib/runfile/concerns/renderable.rb +16 -0
  27. data/lib/runfile/entrypoint.rb +50 -0
  28. data/lib/runfile/exceptions.rb +13 -0
  29. data/lib/runfile/gem_finder.rb +29 -0
  30. data/lib/runfile/initiator.rb +87 -0
  31. data/lib/runfile/meta.rb +65 -0
  32. data/lib/runfile/runner.rb +10 -173
  33. data/lib/runfile/templates/runfile +13 -0
  34. data/lib/runfile/userfile.rb +111 -0
  35. data/lib/runfile/version.rb +2 -2
  36. data/lib/runfile/views/initiator.gtx +28 -0
  37. data/lib/runfile/views/userfile.gtx +93 -0
  38. data/lib/runfile.rb +13 -10
  39. metadata +98 -24
  40. data/bin/run +0 -10
  41. data/bin/run! +0 -16
  42. data/lib/runfile/compatibility.rb +0 -84
  43. data/lib/runfile/docopt_helper.rb +0 -128
  44. data/lib/runfile/dsl.rb +0 -90
  45. data/lib/runfile/refinements.rb +0 -22
  46. data/lib/runfile/runfile_helper.rb +0 -165
  47. data/lib/runfile/settings.rb +0 -34
  48. data/lib/runfile/setup.rb +0 -19
  49. data/lib/runfile/templates/Runfile +0 -16
  50. data/lib/runfile/terminal.rb +0 -32
@@ -0,0 +1,111 @@
1
+ require 'shellwords'
2
+
3
+ module Runfile
4
+ # Represents a single runfile.
5
+ class Userfile
6
+ include Renderable
7
+ include Inspectable
8
+ include DSL
9
+
10
+ attr_reader :code, :name, :path
11
+ attr_writer :context
12
+ alias action_prefix name
13
+
14
+ class << self
15
+ def load_file(path)
16
+ if masterfile? path
17
+ name = nil
18
+ else
19
+ name = File.basename path, '.runfile'
20
+ path = "#{path}.runfile" unless path.end_with? '.runfile'
21
+ end
22
+
23
+ code = File.read path
24
+ new code, name: name, path: path
25
+ end
26
+
27
+ def masterfile?(path)
28
+ Meta::MASTERFILE_NAMES.include? path
29
+ end
30
+ end
31
+
32
+ def initialize(code = nil, name: nil, path: nil)
33
+ @code = code
34
+ @name = name
35
+ @path = path
36
+
37
+ return unless @code
38
+
39
+ if path
40
+ instance_eval @code, @path
41
+ else
42
+ instance_eval @code
43
+ end
44
+ end
45
+
46
+ def inspectable
47
+ { name: name, path: path }
48
+ end
49
+
50
+ def run(argv = [])
51
+ found_delegate = delegate argv[0]
52
+ if found_delegate
53
+ found_delegate.run argv
54
+ else
55
+ run_local argv
56
+ end
57
+ end
58
+
59
+ def context
60
+ @context ||= {}
61
+ end
62
+
63
+ def implicit_title
64
+ title || name
65
+ end
66
+
67
+ # Returns an array of actions that have help defined
68
+ def commands
69
+ actions.values.select(&:help)
70
+ end
71
+
72
+ def delegates
73
+ @delegates ||= (name ? {} : meta.external_files)
74
+ end
75
+
76
+ private
77
+
78
+ def find_action(args)
79
+ acts = actions.values
80
+ acts.find { |a| args[a.name] } ||
81
+ acts.find { |a| args[a.shortcut] } ||
82
+ acts.find { |a| args[a.prefix] } ||
83
+ actions[:default]
84
+ end
85
+
86
+ def run_local(argv)
87
+ Runner.run docopt, argv: argv, version: version do |args|
88
+ action = find_action(args)
89
+ raise ActionNotFound, 'Cannot find action. Is it properly defined?' unless action
90
+
91
+ action.run args
92
+ end
93
+ end
94
+
95
+ def delegate(name)
96
+ return nil unless delegates.has_key? name
97
+
98
+ result = delegates[name]
99
+ result.context = contexts[name]
100
+ result
101
+ end
102
+
103
+ def meta
104
+ @meta ||= Meta.new
105
+ end
106
+
107
+ def docopt
108
+ @docopt ||= render 'userfile', context: self
109
+ end
110
+ end
111
+ end
@@ -1,3 +1,3 @@
1
1
  module Runfile
2
- VERSION = "0.12.0"
3
- end
2
+ VERSION = '1.0.0.rc1'
3
+ end
@@ -0,0 +1,28 @@
1
+ > Runfile
2
+ >
3
+ > Local command line for your projects
4
+ >
5
+ > Usage:
6
+ > run new
7
+ > run example [NAME]
8
+ > run --help | -h
9
+ > run --version
10
+ >
11
+ > Commands:
12
+ > nb`new`
13
+ > Create a new runfile in the current directory
14
+ >
15
+ > nb`example`
16
+ > Copy one of the examples to the current directory
17
+ > Run without arguments to see the list of examples
18
+ >
19
+ > Options:
20
+ > --help, -h
21
+ > Show this message
22
+ >
23
+ > --version
24
+ > Show version number
25
+ >
26
+ > Documentation:
27
+ > bu`https://runfile.dannyb.co`
28
+ >
@@ -0,0 +1,93 @@
1
+ if implicit_title
2
+ = implicit_title
3
+ >
4
+ end
5
+
6
+ if summary
7
+ = implicit_title ? word_wrap(" #{summary}") : word_wrap(summary)
8
+ >
9
+ end
10
+
11
+ > Usage:
12
+ actions.each do |name, action|
13
+ action.implicit_usages.each do |usage|
14
+ = " run #{usage}".rstrip
15
+ end
16
+ end
17
+
18
+ delegates.keys.each do |name|
19
+ = " run #{name}"
20
+ end
21
+
22
+ if delegates.any?
23
+ > run [COMMAND] (--help | -h)
24
+ elsif name
25
+ > run {{ name }} (--help | -h)
26
+ else
27
+ > run (--help | -h)
28
+ end
29
+
30
+ if version
31
+ > run --version
32
+ end
33
+ >
34
+
35
+ if commands.any? || delegates.any?
36
+ > Commands:
37
+ commands.each do |action|
38
+ = " nb`#{action.names.join(', ')}`"
39
+ = word_wrap " #{action.help}"
40
+ >
41
+ end
42
+
43
+ delegates.each do |name, userfile|
44
+ summary = userfile.summary || userfile.title ||
45
+ "Run nu`run #{name} --help` for more information"
46
+
47
+ = " nb`#{name}`"
48
+ = word_wrap " #{summary}"
49
+ >
50
+ end
51
+ end
52
+
53
+ if params.any?
54
+ > Parameters:
55
+ params.each do |name, help|
56
+ = " #{name}"
57
+ = word_wrap " #{help}"
58
+ >
59
+ end
60
+ end
61
+
62
+ > Options:
63
+ options.each do |name, help|
64
+ = " #{name}"
65
+ = word_wrap " #{help}"
66
+ >
67
+ end
68
+ > --help, -h
69
+ > Show this message
70
+ >
71
+ if version
72
+ > --version
73
+ > Show version number
74
+ >
75
+ end
76
+
77
+ if env_vars.any?
78
+ > Environment Variables:
79
+ env_vars.each do |name, help|
80
+ = " #{name}"
81
+ = word_wrap " #{help}"
82
+ end
83
+ >
84
+ end
85
+
86
+
87
+ if examples.any?
88
+ > Examples:
89
+ examples.each do |text|
90
+ = word_wrap " #{text}"
91
+ end
92
+ >
93
+ end
data/lib/runfile.rb CHANGED
@@ -1,10 +1,13 @@
1
- require 'runfile/version'
2
- require 'runfile/terminal'
3
- require 'runfile/refinements'
4
- require 'runfile/settings'
5
- require 'runfile/setup'
6
- require 'runfile/docopt_helper'
7
- require 'runfile/runfile_helper'
8
- require 'runfile/action'
9
- require 'runfile/runner'
10
- require 'runfile/dsl'
1
+ require 'gtx'
2
+ require 'docopt'
3
+ require 'colsole'
4
+ require 'requires'
5
+
6
+ requires 'runfile/exceptions'
7
+ requires 'runfile/concerns'
8
+ requires 'runfile'
9
+
10
+ if ENV['BYEBUG']
11
+ require 'byebug'
12
+ require 'lp'
13
+ end
metadata CHANGED
@@ -1,54 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runfile
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Ben Shitrit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-29 00:00:00.000000000 Z
11
+ date: 2023-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colsole
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.8.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: docopt
15
35
  requirement: !ruby/object:Gem::Requirement
16
36
  requirements:
17
37
  - - "~>"
18
38
  - !ruby/object:Gem::Version
19
- version: '0.5'
39
+ version: '0.6'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.6'
47
+ - !ruby/object:Gem::Dependency
48
+ name: gtx
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.1'
20
54
  type: :runtime
21
55
  prerelease: false
22
56
  version_requirements: !ruby/object:Gem::Requirement
23
57
  requirements:
24
58
  - - "~>"
25
59
  - !ruby/object:Gem::Version
26
- version: '0.5'
27
- description: Build command line applications per project with ease. Rake-inspired,
28
- Docopt inside.
60
+ version: '0.1'
61
+ - !ruby/object:Gem::Dependency
62
+ name: requires
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0.2'
68
+ - - "<"
69
+ - !ruby/object:Gem::Version
70
+ version: '2.0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0.2'
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '2.0'
81
+ description: Build expressive command line utilities for your projects.
29
82
  email: db@dannyben.com
30
83
  executables:
31
- - run
32
- - run!
84
+ - runn
33
85
  extensions: []
34
86
  extra_rdoc_files: []
35
87
  files:
36
88
  - README.md
37
- - bin/run
38
- - bin/run!
89
+ - bin/runn
90
+ - examples/default-action-2/runfile
91
+ - examples/default-action-2/server.runfile
92
+ - examples/default-action/runfile
93
+ - examples/different-name/runfile.rb
94
+ - examples/example-multiline/runfile
95
+ - examples/example/runfile
96
+ - examples/execute/runfile
97
+ - examples/execute/server.runfile
98
+ - examples/full/runfile
99
+ - examples/import/more_tasks/spec.runfile
100
+ - examples/import/runfile
101
+ - examples/import/tasks/server.runfile
102
+ - examples/minimal/runfile
103
+ - examples/multiple-runfiles/runfile
104
+ - examples/multiple-runfiles/server.runfile
105
+ - examples/named-only/deploy.runfile
106
+ - examples/named-only/server.runfile
107
+ - examples/naval-fate/runfile
108
+ - examples/shortcut/runfile
39
109
  - lib/runfile.rb
40
110
  - lib/runfile/action.rb
41
- - lib/runfile/compatibility.rb
42
- - lib/runfile/docopt_helper.rb
43
- - lib/runfile/dsl.rb
44
- - lib/runfile/refinements.rb
45
- - lib/runfile/runfile_helper.rb
111
+ - lib/runfile/concerns/dsl.rb
112
+ - lib/runfile/concerns/inspectable.rb
113
+ - lib/runfile/concerns/renderable.rb
114
+ - lib/runfile/entrypoint.rb
115
+ - lib/runfile/exceptions.rb
116
+ - lib/runfile/gem_finder.rb
117
+ - lib/runfile/initiator.rb
118
+ - lib/runfile/meta.rb
46
119
  - lib/runfile/runner.rb
47
- - lib/runfile/settings.rb
48
- - lib/runfile/setup.rb
49
- - lib/runfile/templates/Runfile
50
- - lib/runfile/terminal.rb
120
+ - lib/runfile/templates/runfile
121
+ - lib/runfile/userfile.rb
51
122
  - lib/runfile/version.rb
123
+ - lib/runfile/views/initiator.gtx
124
+ - lib/runfile/views/userfile.gtx
52
125
  homepage: https://github.com/DannyBen/runfile
53
126
  licenses:
54
127
  - MIT
@@ -57,6 +130,7 @@ metadata:
57
130
  changelog_uri: https://github.com/DannyBen/runfile/blob/master/CHANGELOG.md
58
131
  source_code_uri: https://github.com/DannyBen/runfile
59
132
  bug_tracker_uri: https://github.com/DannyBen/runfile/issues
133
+ rubygems_mfa_required: 'true'
60
134
  post_install_message:
61
135
  rdoc_options: []
62
136
  require_paths:
@@ -65,15 +139,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
139
  requirements:
66
140
  - - ">="
67
141
  - !ruby/object:Gem::Version
68
- version: 2.4.0
142
+ version: '2.7'
69
143
  required_rubygems_version: !ruby/object:Gem::Requirement
70
144
  requirements:
71
- - - ">="
145
+ - - ">"
72
146
  - !ruby/object:Gem::Version
73
- version: '0'
147
+ version: 1.3.1
74
148
  requirements: []
75
- rubygems_version: 3.2.25
149
+ rubygems_version: 3.4.5
76
150
  signing_key:
77
151
  specification_version: 4
78
- summary: If Rake and Docopt had a baby
152
+ summary: Local command line for your projects
79
153
  test_files: []
data/bin/run DELETED
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'runfile'
4
-
5
- # for dev
6
- # require File.dirname(__FILE__) + "/../lib/runfile"
7
-
8
- include Runfile::DSL
9
-
10
- Runfile::Runner.instance.execute ARGV
data/bin/run! DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # This is needed in cases when you are running "run!" from a folder
4
- # that contains a Gemfile on a machine with RVM.
5
- # It will disable attempting to run in the current context
6
- # Source: https://rvm.io/integration/bundler
7
- ENV['NOEXEC_DISABLE'] = '1'
8
-
9
- require 'runfile'
10
-
11
- # for dev
12
- # require File.dirname(__FILE__) + "/../lib/runfile"
13
-
14
- include Runfile::DSL
15
-
16
- Runfile::Runner.instance.execute ARGV, false
@@ -1,84 +0,0 @@
1
- # This file provides a compatibility layer for version 0.12.0 so that
2
- # all the Colsole methods (say, resay...) and ExecHandler methods
3
- # (run, run_bg...) are still available
4
- # Simply require 'runfile/compatibility' to have the same functionality
5
- # as older versions.
6
- #
7
- # Notes:
8
- # - You need to have the colsole gem installed
9
- # - Requiring this file also includes it (see the end of the file).
10
- # - This functinality will be removed in future versions
11
- # - More info: https://github.com/DannyBen/runfile/pull/46
12
- require 'colsole'
13
-
14
- module Runfile
15
- module Compatibilty
16
- include Colsole
17
-
18
- # Run a command, wait until it is done and continue
19
- def run(cmd)
20
- cmd = @before_run_block.call(cmd) if @before_run_block
21
- return false unless cmd
22
- say "!txtgrn!> #{cmd}" unless Runfile.quiet
23
- system cmd
24
- @after_run_block.call(cmd) if @after_run_block
25
- end
26
-
27
- # Run a command, wait until it is done, then exit
28
- def run!(cmd)
29
- cmd = @before_run_block.call(cmd) if @before_run_block
30
- return false unless cmd
31
- say "!txtgrn!> #{cmd}" unless Runfile.quiet
32
- exec cmd
33
- end
34
-
35
- # Run a command in the background, optionally log to a log file and save
36
- # the process ID in a pid file
37
- def run_bg(cmd, pid: nil, log: '/dev/null')
38
- cmd = @before_run_block.call(cmd) if @before_run_block
39
- return false unless cmd
40
- full_cmd = "exec #{cmd} >#{log} 2>&1"
41
- say "!txtgrn!> #{full_cmd}" unless Runfile.quiet
42
- process = IO.popen "exec #{cmd} >#{log} 2>&1"
43
- File.write pidfile(pid), process.pid if pid
44
- @after_run_block.call(cmd) if @after_run_block
45
- return process.pid
46
- end
47
-
48
- # Stop a command started with 'run_bg'. Provide the name of he pid file you
49
- # used in 'run_bg'
50
- def stop_bg(pid)
51
- file = pidfile(pid)
52
- if File.exist? file
53
- pid = File.read file
54
- File.delete file
55
- run "kill -s TERM #{pid}"
56
- else
57
- say "!txtred!PID file not found." unless Runfile.quiet
58
- end
59
- end
60
-
61
- # Set a block to be called before each run
62
- def before_run(&block)
63
- @before_run_block = block
64
- end
65
-
66
- # Set a block to be called after each run
67
- def after_run(&block)
68
- @after_run_block = block
69
- end
70
-
71
- private
72
-
73
- def pid_dir
74
- defined?(Runfile.pid_dir) ? Runfile.pid_dir : nil
75
- end
76
-
77
- def pidfile(pid)
78
- pid_dir ? "#{pid_dir}/#{pid}.pid" : "#{pid}.pid"
79
- end
80
-
81
- end
82
- end
83
-
84
- include Runfile::Compatibilty
@@ -1,128 +0,0 @@
1
- require 'docopt'
2
-
3
- module Runfile
4
- # The DocoptHelper class handles the dynamic generation of the
5
- # docopt document and the docopt part of the execution (meaning,
6
- # to call Docopt so it returns the parsed arguments or halts with
7
- # usage message).
8
- class DocoptHelper
9
- using Refinements
10
-
11
- # The constructor expects to an object that responds to all the
12
- # textual details needed to generate a docopt document (name, version,
13
- # summary, options) and an array of Action objects.
14
- # The superspace argument will be the name of runfile, in case we
15
- # are running a named.runfile. It is only needed to generate the
16
- # proper `run superspace (-h|--help|--version)` line
17
- def initialize(options)
18
- @superspace = options.superspace
19
- @name = options.name
20
- @version = options.version
21
- @summary = options.summary
22
- @actions = options.actions
23
- @options = options.options
24
- @params = options.params
25
- @env_vars = options.env_vars
26
- @examples = options.examples
27
- end
28
-
29
- # Generate a document based on all the actions, help messages
30
- # and options we have collected from the Runfile DSL.
31
- def docopt
32
- width = Terminal.width
33
- doc = []
34
- doc << (@version ? "#{@name} #{@version}" : "#{@name}")
35
- doc << "#{@summary}" if @summary
36
- doc += docopt_usage
37
- doc += docopt_commands width
38
- doc += docopt_options width
39
- doc += docopt_params width
40
- doc += docopt_env_vars width
41
- doc += docopt_examples width
42
- doc.join "\n"
43
- end
44
-
45
- # Return all docopt lines for the 'Usage' section
46
- def docopt_usage
47
- doc = ["\nUsage:"];
48
- @actions.each do |_name, action|
49
- doc << " run #{action.usage}" unless action.usage == false
50
- end
51
- basic_flags = @version ? "(-h|--help|--version)" : "(-h|--help)"
52
- if @superspace
53
- doc << " run #{@superspace} #{basic_flags}\n"
54
- else
55
- doc << " run #{basic_flags}\n"
56
- end
57
- doc
58
- end
59
-
60
- # Return all docopt lines for the 'Commands' section
61
- def docopt_commands(width)
62
- doc = []
63
- caption_printed = false
64
- @actions.each do |_name, action|
65
- action.help or next
66
- doc << "Commands:" unless caption_printed
67
- caption_printed = true
68
- helpline = " #{action.help}"
69
- wrapped = helpline.word_wrap width
70
- doc << " #{action.usage}\n#{wrapped}\n" unless action.usage == false
71
- end
72
- doc
73
- end
74
-
75
- # Return all docopt lines for the various 'Options' sections
76
- def docopt_options(width)
77
- @options['Options'] = {} unless @options['Options']
78
- @options['Options']['-h --help'] = 'Show this screen'
79
- @options['Options']['--version'] = 'Show version number' if @version
80
- section_block @options, width
81
- end
82
-
83
- # Return all docopt params for 'Params' section
84
- def docopt_params(width)
85
- section_block @params, width
86
- end
87
-
88
- # Return all docopt params for 'Environment Variables' section
89
- def docopt_env_vars(width)
90
- section_block @env_vars, width
91
- end
92
-
93
- # Return all docopt lines for the 'Examples' section
94
- def docopt_examples(width)
95
- return [] if @examples.empty?
96
-
97
- doc = ["Examples:"]
98
- base_command = @superspace ? "run #{@superspace}" : "run"
99
- @examples.each do |command|
100
- helpline = " #{base_command} #{command}"
101
- wrapped = helpline.word_wrap width
102
- doc << "#{wrapped}"
103
- end
104
- doc
105
- end
106
-
107
- # Return a generic block containing scope section (e.g. "Options"),
108
- # followed by key value paragraphs.
109
- def section_block(definitions, width)
110
- doc = []
111
- definitions.each do |scope, values|
112
- doc << "#{scope}:"
113
- values.each do |label, text|
114
- helpline = " #{text}"
115
- wrapped = helpline.word_wrap width
116
- doc << " #{label}\n#{wrapped}\n"
117
- end
118
- end
119
- doc
120
- end
121
-
122
- # Call the docopt handler, which will either return a parsed
123
- # arguments list, or halt execution and show usage.
124
- def args(argv)
125
- Docopt.docopt(docopt, version: @version, argv:argv)
126
- end
127
- end
128
- end