ronin-core 0.1.0.beta1

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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +41 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +160 -0
  7. data/.ruby-version +1 -0
  8. data/.yardopts +1 -0
  9. data/COPYING.txt +165 -0
  10. data/ChangeLog.md +11 -0
  11. data/Gemfile +30 -0
  12. data/README.md +299 -0
  13. data/Rakefile +34 -0
  14. data/examples/ruby_shell.rb +11 -0
  15. data/gemspec.yml +28 -0
  16. data/lib/ronin/core/class_registry.rb +246 -0
  17. data/lib/ronin/core/cli/command.rb +87 -0
  18. data/lib/ronin/core/cli/command_shell/command.rb +110 -0
  19. data/lib/ronin/core/cli/command_shell.rb +345 -0
  20. data/lib/ronin/core/cli/generator/options/author.rb +106 -0
  21. data/lib/ronin/core/cli/generator/options/description.rb +54 -0
  22. data/lib/ronin/core/cli/generator/options/reference.rb +60 -0
  23. data/lib/ronin/core/cli/generator/options/summary.rb +54 -0
  24. data/lib/ronin/core/cli/generator.rb +238 -0
  25. data/lib/ronin/core/cli/logging.rb +59 -0
  26. data/lib/ronin/core/cli/options/param.rb +68 -0
  27. data/lib/ronin/core/cli/options/values/arches.rb +45 -0
  28. data/lib/ronin/core/cli/options/values/oses.rb +32 -0
  29. data/lib/ronin/core/cli/printing/arch.rb +71 -0
  30. data/lib/ronin/core/cli/printing/metadata.rb +113 -0
  31. data/lib/ronin/core/cli/printing/os.rb +54 -0
  32. data/lib/ronin/core/cli/printing/params.rb +69 -0
  33. data/lib/ronin/core/cli/ruby_shell.rb +131 -0
  34. data/lib/ronin/core/cli/shell.rb +186 -0
  35. data/lib/ronin/core/git.rb +73 -0
  36. data/lib/ronin/core/home.rb +86 -0
  37. data/lib/ronin/core/metadata/authors/author.rb +241 -0
  38. data/lib/ronin/core/metadata/authors.rb +120 -0
  39. data/lib/ronin/core/metadata/description.rb +100 -0
  40. data/lib/ronin/core/metadata/id.rb +88 -0
  41. data/lib/ronin/core/metadata/references.rb +87 -0
  42. data/lib/ronin/core/metadata/summary.rb +78 -0
  43. data/lib/ronin/core/metadata/version.rb +74 -0
  44. data/lib/ronin/core/params/exceptions.rb +38 -0
  45. data/lib/ronin/core/params/mixin.rb +317 -0
  46. data/lib/ronin/core/params/param.rb +137 -0
  47. data/lib/ronin/core/params/types/boolean.rb +64 -0
  48. data/lib/ronin/core/params/types/enum.rb +107 -0
  49. data/lib/ronin/core/params/types/float.rb +68 -0
  50. data/lib/ronin/core/params/types/integer.rb +100 -0
  51. data/lib/ronin/core/params/types/numeric.rb +106 -0
  52. data/lib/ronin/core/params/types/regexp.rb +67 -0
  53. data/lib/ronin/core/params/types/string.rb +118 -0
  54. data/lib/ronin/core/params/types/type.rb +54 -0
  55. data/lib/ronin/core/params/types/uri.rb +72 -0
  56. data/lib/ronin/core/params/types.rb +62 -0
  57. data/lib/ronin/core/params.rb +19 -0
  58. data/lib/ronin/core/version.rb +24 -0
  59. data/ronin-core.gemspec +59 -0
  60. data/spec/class_registry_spec.rb +224 -0
  61. data/spec/cli/command_shell/command_spec.rb +113 -0
  62. data/spec/cli/command_shell_spec.rb +1114 -0
  63. data/spec/cli/command_spec.rb +16 -0
  64. data/spec/cli/fixtures/irb_command +8 -0
  65. data/spec/cli/fixtures/template/dir/file1.txt +1 -0
  66. data/spec/cli/fixtures/template/dir/file2.txt +1 -0
  67. data/spec/cli/fixtures/template/file.erb +1 -0
  68. data/spec/cli/fixtures/template/file.txt +1 -0
  69. data/spec/cli/generator/options/author_spec.rb +121 -0
  70. data/spec/cli/generator/options/description_spec.rb +45 -0
  71. data/spec/cli/generator/options/reference_spec.rb +53 -0
  72. data/spec/cli/generator/options/summary_spec.rb +45 -0
  73. data/spec/cli/generator_spec.rb +244 -0
  74. data/spec/cli/logging_spec.rb +95 -0
  75. data/spec/cli/options/param_spec.rb +67 -0
  76. data/spec/cli/options/values/arches_spec.rb +62 -0
  77. data/spec/cli/printing/arch_spec.rb +130 -0
  78. data/spec/cli/printing/metadata_spec.rb +211 -0
  79. data/spec/cli/printing/os_spec.rb +64 -0
  80. data/spec/cli/printing/params_spec.rb +63 -0
  81. data/spec/cli/ruby_shell.rb +99 -0
  82. data/spec/cli/shell_spec.rb +211 -0
  83. data/spec/fixtures/example_class_registry/base_class.rb +9 -0
  84. data/spec/fixtures/example_class_registry/classes/loaded_class.rb +9 -0
  85. data/spec/fixtures/example_class_registry/classes/name_mismatch.rb +9 -0
  86. data/spec/fixtures/example_class_registry/classes/no_module.rb +4 -0
  87. data/spec/fixtures/example_class_registry.rb +8 -0
  88. data/spec/git_spec.rb +58 -0
  89. data/spec/home_spec.rb +64 -0
  90. data/spec/metadata/authors/author_spec.rb +335 -0
  91. data/spec/metadata/authors_spec.rb +126 -0
  92. data/spec/metadata/description_spec.rb +74 -0
  93. data/spec/metadata/id_spec.rb +92 -0
  94. data/spec/metadata/references_spec.rb +100 -0
  95. data/spec/metadata/summary_spec.rb +74 -0
  96. data/spec/metadata/version_spec.rb +72 -0
  97. data/spec/params/mixin_spec.rb +484 -0
  98. data/spec/params/param_spec.rb +164 -0
  99. data/spec/params/types/boolean_spec.rb +56 -0
  100. data/spec/params/types/enum_spec.rb +94 -0
  101. data/spec/params/types/float_spec.rb +107 -0
  102. data/spec/params/types/integer_spec.rb +155 -0
  103. data/spec/params/types/numeric_spec.rb +138 -0
  104. data/spec/params/types/regexp_spec.rb +64 -0
  105. data/spec/params/types/string_spec.rb +174 -0
  106. data/spec/params/types/type_spec.rb +14 -0
  107. data/spec/params/types/uri_spec.rb +62 -0
  108. data/spec/spec_helper.rb +11 -0
  109. metadata +252 -0
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+ require 'ronin/core/params/types/enum'
3
+
4
+ describe Ronin::Core::Params::Types::Enum do
5
+ let(:values) { [:one, :two, :three] }
6
+
7
+ subject { described_class.new(values) }
8
+
9
+ describe "#initialize" do
10
+ it "must set #values" do
11
+ expect(subject.values).to eq(values)
12
+ end
13
+
14
+ context "when given an empty Array" do
15
+ it do
16
+ expect {
17
+ described_class.new([])
18
+ }.to raise_error(ArgumentError,"cannot initialize an empty Enum")
19
+ end
20
+ end
21
+ end
22
+
23
+ describe ".[]" do
24
+ subject { described_class[*values] }
25
+
26
+ it "must return a new #{described_class}" do
27
+ expect(subject).to be_kind_of(described_class)
28
+ end
29
+
30
+ it "must set #values to the given arguments" do
31
+ expect(subject.values).to eq(values)
32
+ end
33
+
34
+ context "when given no arguments" do
35
+ it do
36
+ expect {
37
+ described_class[]
38
+ }.to raise_error(ArgumentError,"cannot initialize an empty Enum")
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "#coerce" do
44
+ context "when givne a Symbol" do
45
+ context "and when the Symbol is in #values" do
46
+ let(:value) { values[1] }
47
+
48
+ it "must return that Symbol" do
49
+ expect(subject.coerce(value)).to eq(value)
50
+ end
51
+ end
52
+
53
+ context "but the Symbol is not within #values" do
54
+ let(:value) { :foo }
55
+
56
+ it do
57
+ expect {
58
+ subject.coerce(value)
59
+ }.to raise_error(Ronin::Core::Params::ValidationError,"unknown value (#{value.inspect})")
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when given a String" do
65
+ context "and it maps to one of the Symbols in #values" do
66
+ let(:value) { values[1].to_s }
67
+
68
+ it "must return the Symbol version of the String" do
69
+ expect(subject.coerce(value)).to eq(values[1])
70
+ end
71
+ end
72
+
73
+ context "but it does not map to any of the Symbols in #values" do
74
+ let(:value) { "foo" }
75
+
76
+ it "must return the Symbol version of the String" do
77
+ expect {
78
+ subject.coerce(value)
79
+ }.to raise_error(Ronin::Core::Params::ValidationError,"unknown value (#{value.inspect})")
80
+ end
81
+ end
82
+ end
83
+
84
+ context "when given a non-Symbol and non-String object" do
85
+ let(:value) { Object.new }
86
+
87
+ it do
88
+ expect {
89
+ subject.coerce(value)
90
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value must be either a Symbol or a String (#{value.inspect})")
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+ require 'ronin/core/params/types/float'
3
+
4
+ describe Ronin::Core::Params::Types::Float do
5
+ describe "#coerce" do
6
+ context "when given a Float" do
7
+ let(:value) { 0.5 }
8
+
9
+ it "must return the Float value" do
10
+ expect(subject.coerce(value)).to be(value)
11
+ end
12
+ end
13
+
14
+ context "when given a String" do
15
+ context "and it is in the format of a decimal number" do
16
+ let(:value) { '0.5' }
17
+
18
+ it "must parse the String as a Float" do
19
+ expect(subject.coerce(value)).to eq(value.to_f)
20
+ end
21
+
22
+ context "but it starts with a '+'" do
23
+ let(:value) { '+0.5' }
24
+
25
+ it "must parse the String as a Float" do
26
+ expect(subject.coerce(value)).to eq(value[1..].to_f)
27
+ end
28
+ end
29
+
30
+ context "but it starts with a '-'" do
31
+ let(:value) { '-0.5' }
32
+
33
+ it "must parse the String as a negative Float" do
34
+ expect(subject.coerce(value)).to eq(-value[1..].to_f)
35
+ end
36
+ end
37
+ end
38
+
39
+ context "and it is in the fomrat of a whole number" do
40
+ let(:value) { '1' }
41
+
42
+ it "must parse the String as a Float" do
43
+ expect(subject.coerce(value)).to eq(value.to_f)
44
+ end
45
+
46
+ context "but it starts with a '+'" do
47
+ let(:value) { '+1' }
48
+
49
+ it "must parse the String as a Float" do
50
+ expect(subject.coerce(value)).to eq(value[1..].to_f)
51
+ end
52
+ end
53
+
54
+ context "but it starts with a '-'" do
55
+ let(:value) { '-1' }
56
+
57
+ it "must parse the String as a negative Float" do
58
+ expect(subject.coerce(value)).to eq(-value[1..].to_f)
59
+ end
60
+ end
61
+ end
62
+
63
+ context "but it contains non-numeric characters" do
64
+ let(:value) { "foo" }
65
+
66
+ it do
67
+ expect {
68
+ subject.coerce(value)
69
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value contains non-numeric characters (#{value.inspect})")
70
+ end
71
+ end
72
+ end
73
+
74
+ context "when given a non-Float and non-String object" do
75
+ context "and it defines a #to_f method" do
76
+ module TestTypesFloat
77
+ class ObjectWithToF
78
+ def to_f
79
+ 0.5
80
+ end
81
+ end
82
+ end
83
+
84
+ let(:value) { TestTypesFloat::ObjectWithToF.new }
85
+
86
+ it "must call the #to_f method on the value" do
87
+ expect(subject.coerce(value)).to eq(value.to_f)
88
+ end
89
+ end
90
+
91
+ context "but it does not define a #to_f method" do
92
+ module TestTypesFloat
93
+ class ObjectWithoutToF
94
+ end
95
+ end
96
+
97
+ let(:value) { TestTypesFloat::ObjectWithoutToF.new }
98
+
99
+ it do
100
+ expect {
101
+ subject.coerce(value)
102
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value does not define a #to_f method (#{value.inspect})")
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+ require 'ronin/core/params/types/integer'
3
+
4
+ describe Ronin::Core::Params::Types::Integer do
5
+ describe "#coerce" do
6
+ context "when given an Integer" do
7
+ let(:value) { 42 }
8
+
9
+ it "must return the Integer value" do
10
+ expect(subject.coerce(value)).to eq(value)
11
+ end
12
+ end
13
+
14
+ context "when given a String" do
15
+ context "and the String contains decimal digits" do
16
+ let(:value) { "42" }
17
+
18
+ it "must parse the String as base 10 and return an Integer" do
19
+ expect(subject.coerce(value)).to eq(value.to_i)
20
+ end
21
+
22
+ context "and the String also starts with a '+'" do
23
+ let(:value) { "+42" }
24
+
25
+ it "must parse the String as base 10 and return an Integer" do
26
+ expect(subject.coerce(value)).to eq(value.to_i)
27
+ end
28
+ end
29
+
30
+ context "and the String also starts with a '-'" do
31
+ let(:value) { "-42" }
32
+
33
+ it "must parse the String as base 10 and return a negative Integer" do
34
+ expect(subject.coerce(value)).to eq(-value[1..].to_i)
35
+ end
36
+ end
37
+ end
38
+
39
+ context "and the String contains hexadecimal digits" do
40
+ let(:value) { "ff" }
41
+
42
+ it "must parse the String as base 16 and return an Integer" do
43
+ expect(subject.coerce(value)).to eq(value.to_i(16))
44
+ end
45
+
46
+ context "and the String also starts with a '+'" do
47
+ let(:value) { "+ff" }
48
+
49
+ it "must parse the String as base 16 and return an Integer" do
50
+ expect(subject.coerce(value)).to eq(value.to_i(16))
51
+ end
52
+ end
53
+
54
+ context "and the String also starts with a '-'" do
55
+ let(:value) { "-ff" }
56
+
57
+ it "must parse the String as base 16 and return a negative Integer" do
58
+ expect(subject.coerce(value)).to eq(-value[1..].to_i(16))
59
+ end
60
+ end
61
+
62
+ context "but the String also starts with '0x'" do
63
+ let(:value) { "0xff" }
64
+
65
+ it "must remove the leading '0x' before parsing the String" do
66
+ expect(subject.coerce(value)).to eq(value[2..].to_i(16))
67
+ end
68
+
69
+ context "and the String also starts with a '+'" do
70
+ let(:value) { "+0xff" }
71
+
72
+ it "must parse the String as base 16 and return a Integer" do
73
+ expect(subject.coerce(value)).to eq(value[3..].to_i(16))
74
+ end
75
+ end
76
+
77
+ context "and the String also starts with a '-'" do
78
+ let(:value) { "-0xff" }
79
+
80
+ it "must parse the String as base 16 and return a negative Integer" do
81
+ expect(subject.coerce(value)).to eq(-value[3..].to_i(16))
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ context "and the String starts with '0b' and only contains chars 0 and 1" do
88
+ let(:value) { "0b111" }
89
+
90
+ it "must parse the String as binary and return an Integer" do
91
+ expect(subject.coerce(value)).to eq(value[2..].to_i(2))
92
+ end
93
+
94
+ context "and the String also starts with a '+'" do
95
+ let(:value) { "+0b111" }
96
+
97
+ it "must parse the String as base 16 and return a Integer" do
98
+ expect(subject.coerce(value)).to eq(value[3..].to_i(2))
99
+ end
100
+ end
101
+
102
+ context "and the String also starts with a '-'" do
103
+ let(:value) { "-0b111" }
104
+
105
+ it "must parse the String as base 16 and return a negative Integer" do
106
+ expect(subject.coerce(value)).to eq(-value[3..].to_i(2))
107
+ end
108
+ end
109
+ end
110
+
111
+ context "but the String contains non-numeric characters" do
112
+ let(:value) { "bar" }
113
+
114
+ it do
115
+ expect {
116
+ subject.coerce(value)
117
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value contains non-numeric characters (#{value.inspect})")
118
+ end
119
+ end
120
+ end
121
+
122
+ context "when given a non-Integer and non-String Object" do
123
+ context "and it defines a #to_i method" do
124
+ module TestTypesInteger
125
+ class ObjectWithToI
126
+ def to_i
127
+ 42
128
+ end
129
+ end
130
+ end
131
+
132
+ let(:value) { TestTypesInteger::ObjectWithToI.new }
133
+
134
+ it "must call #to_i on the given value" do
135
+ expect(subject.coerce(value)).to eq(value.to_i)
136
+ end
137
+ end
138
+
139
+ context "but it does not define a #to_i method" do
140
+ module TestTypesInteger
141
+ class ObjectWithoutToI
142
+ end
143
+ end
144
+
145
+ let(:value) { TestTypesInteger::ObjectWithoutToI.new }
146
+
147
+ it do
148
+ expect {
149
+ subject.coerce(value)
150
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value does not define a #to_i method (#{value.inspect})")
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+ require 'ronin/core/params/types/numeric'
3
+
4
+ describe Ronin::Core::Params::Types::Numeric do
5
+ describe "#initialize" do
6
+ it "must default #min to nil" do
7
+ expect(subject.min).to be(nil)
8
+ end
9
+
10
+ it "must default #max to nil" do
11
+ expect(subject.max).to be(nil)
12
+ end
13
+
14
+ it "must default #range to nil" do
15
+ expect(subject.range).to be(nil)
16
+ end
17
+
18
+ context "when given the min: keyword argument" do
19
+ let(:min) { 42 }
20
+
21
+ subject { described_class.new(min: min) }
22
+
23
+ it "must set #min" do
24
+ expect(subject.min).to eq(min)
25
+ end
26
+ end
27
+
28
+ context "when given the max: keyword argument" do
29
+ let(:max) { 42 }
30
+
31
+ subject { described_class.new(max: max) }
32
+
33
+ it "must set #max" do
34
+ expect(subject.max).to eq(max)
35
+ end
36
+ end
37
+
38
+ context "when given the range: keyword argument" do
39
+ let(:range) { 1..42 }
40
+
41
+ subject { described_class.new(range: range) }
42
+
43
+ it "must set #range" do
44
+ expect(subject.range).to eq(range)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "#coerce" do
50
+ context "when #range is set" do
51
+ let(:range) { 1..10 }
52
+
53
+ subject { described_class.new(range: range) }
54
+
55
+ context "and when the given number is within the range" do
56
+ let(:value) { 5 }
57
+
58
+ it "must return the number" do
59
+ expect(subject.coerce(value)).to eq(value)
60
+ end
61
+ end
62
+
63
+ context "but the given number is not within the range" do
64
+ let(:value) { 20 }
65
+
66
+ it do
67
+ expect {
68
+ subject.coerce(value)
69
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value is not within the range of acceptable values #{range.begin}-#{range.end} (#{value.inspect})")
70
+ end
71
+ end
72
+ end
73
+
74
+ context "when #min is set" do
75
+ let(:min) { 10 }
76
+
77
+ subject { described_class.new(min: min) }
78
+
79
+ context "and when then value is above the #min value" do
80
+ let(:value) { 20 }
81
+
82
+ it "must return the value" do
83
+ expect(subject.coerce(value)).to eq(value)
84
+ end
85
+ end
86
+
87
+ context "and when then value is equal to the #min value" do
88
+ let(:value) { min }
89
+
90
+ it "must return the value" do
91
+ expect(subject.coerce(value)).to eq(value)
92
+ end
93
+ end
94
+
95
+ context "but when the value is below the #min value" do
96
+ let(:value) { 5 }
97
+
98
+ it do
99
+ expect {
100
+ subject.coerce(value)
101
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value is below minimum value of #{min} (#{value.inspect})")
102
+ end
103
+ end
104
+ end
105
+
106
+ context "when #max is set" do
107
+ let(:max) { 10 }
108
+
109
+ subject { described_class.new(max: max) }
110
+
111
+ context "and when then value is below the #max value" do
112
+ let(:value) { 5 }
113
+
114
+ it "must return the value" do
115
+ expect(subject.coerce(value)).to eq(value)
116
+ end
117
+ end
118
+
119
+ context "and when then value is equal to the #max value" do
120
+ let(:value) { max }
121
+
122
+ it "must return the value" do
123
+ expect(subject.coerce(value)).to eq(value)
124
+ end
125
+ end
126
+
127
+ context "but when the value is above the #max value" do
128
+ let(:value) { 20 }
129
+
130
+ it do
131
+ expect {
132
+ subject.coerce(value)
133
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value is above maximum value of #{max} (#{value.inspect})")
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+ require 'ronin/core/params/types/regexp'
3
+
4
+ describe Ronin::Core::Params::Types::Regexp do
5
+ describe "#coerce" do
6
+ context "when given a Regexp value" do
7
+ let(:value) { /foo/ }
8
+
9
+ it "must return the Regexp" do
10
+ expect(subject.coerce(value)).to eq(value)
11
+ end
12
+ end
13
+
14
+ context "when given a String value" do
15
+ context "and the String is of the format '/.../'" do
16
+ let(:value) { "/foo/" }
17
+
18
+ it "must parse the contents of the String as a Regexp" do
19
+ expect(subject.coerce(value)).to eq(Regexp.new(value[1..-2]))
20
+ end
21
+ end
22
+
23
+ context "but the String does not parse to a valid Regexp" do
24
+ let(:value) { "/foo[/" }
25
+
26
+ it do
27
+ expect {
28
+ subject.coerce(value)
29
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value is not a valid Regexp (#{value.inspect})")
30
+ end
31
+ end
32
+
33
+ context "but the String does not start with a '/' character" do
34
+ let(:value) { "foo/" }
35
+
36
+ it do
37
+ expect {
38
+ subject.coerce(value)
39
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value must be of the format '/.../' (#{value.inspect})")
40
+ end
41
+ end
42
+
43
+ context "but the String does not end with a '/' character" do
44
+ let(:value) { "/foo" }
45
+
46
+ it do
47
+ expect {
48
+ subject.coerce(value)
49
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value must be of the format '/.../' (#{value.inspect})")
50
+ end
51
+ end
52
+ end
53
+
54
+ context "when given a non-Regexp and non-String value" do
55
+ let(:value) { Object.new }
56
+
57
+ it do
58
+ expect {
59
+ subject.coerce(value)
60
+ }.to raise_error(Ronin::Core::Params::ValidationError,"value must be either a String or a Regexp (#{value.inspect})")
61
+ end
62
+ end
63
+ end
64
+ end