ydim 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ebd5360c95d8427ee4b3a093102866fce2d3e482
4
+ data.tar.gz: c2c5e41885205634f338502a40c044eaa7b090c8
5
+ SHA512:
6
+ metadata.gz: 24534e21dd9a128df999e34922820fa4740eb85faedfb0b3069c16802628929491d89a8786d663ae5b918cc61a02c75c33cc10a75be556f3eddea14d44659a0e
7
+ data.tar.gz: 450bc76a0b030402f9011884cb1392946864764841d8bca4240be27ac7ee03bff80b70d6424710153686ebba07211d9ac21a500599aa55a4583f8ae99005b52f
@@ -0,0 +1 @@
1
+ *.rb diff
@@ -0,0 +1,6 @@
1
+ vendor/
2
+ coverage/
3
+ pkg/
4
+ .bundle
5
+ .ruby-version
6
+ Gemfile.lock
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+
3
+ bundler_args: --without debugger
4
+
5
+ cache: bundler
6
+ sudo: false
7
+
8
+ before_install:
9
+ - gem --version
10
+
11
+ script: bundle exec test/suite.rb
12
+
13
+ rvm:
14
+ - 2.1
15
+ - 2.2
16
+ - 2.3.0
17
+ - ruby-head
18
+
19
+ matrix:
20
+ allow_failures:
21
+ - rvm: ruby-head
22
+
23
+ notifications:
24
+ email:
25
+ recipients:
26
+ - ngiger@ywesee.com
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :debugger do
5
+ if RUBY_VERSION.match(/^1/)
6
+ gem 'pry-debugger'
7
+ else
8
+ gem 'pry-byebug'
9
+ gem 'pry-doc'
10
+ end
11
+ end
@@ -1,6 +1,11 @@
1
- === 1.0.0 / 2010-12-20
1
+ === 1.0.1 / 10.05.2016
2
2
 
3
- * 1 major enhancement
3
+ * Works only for Ruby version >= 2.1.0
4
+ * Replaced hoe by bundler/gem_tasks
5
+
6
+ === 1.0.0 / 20.12.2010
7
+
8
+ * Version 1.0
4
9
 
5
10
  * Birthday!
6
11
 
data/Rakefile CHANGED
@@ -1,28 +1,21 @@
1
- # -*- ruby -*-
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'ydim/version'
6
+ require "bundler/gem_tasks"
7
+ require 'rspec/core/rake_task'
2
8
 
3
- require 'rubygems'
4
- require 'hoe'
9
+ RSpec::Core::RakeTask.new(:spec)
5
10
 
6
- # Hoe.plugin :compiler
7
- # Hoe.plugin :cucumberfeatures
8
- # Hoe.plugin :gem_prelude_sucks
9
- # Hoe.plugin :inline
10
- # Hoe.plugin :inline
11
- # Hoe.plugin :manifest
12
- # Hoe.plugin :newgem
13
- # Hoe.plugin :racc
14
- # Hoe.plugin :rubyforge
15
- # Hoe.plugin :rubyforge
16
- # Hoe.plugin :website
11
+ # dependencies are now declared in ydim.gemspec
17
12
 
18
- Hoe.spec 'ydim' do
19
- # HEY! If you fill these out in ~/.hoe_template/Rakefile.erb then
20
- # you'll never have to touch them again!
21
- # (delete this comment too, of course)
22
-
23
- developer('Masaomi Hatakeyama, Zeno R.R. Davatz', 'mhatakeyama@ywesee.com, zdavatz@ywesee.com')
24
-
25
- # self.rubyforge_name = 'ydimx' # if different than 'ydim'
13
+ desc 'Offer a gem task like hoe'
14
+ task :gem => :build do
15
+ Rake::Task[:build].invoke
26
16
  end
27
17
 
28
- # vim: syntax=ruby
18
+ task :spec => :clean
19
+
20
+ require 'rake/clean'
21
+ CLEAN.include FileList['pkg/*.gem']
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ puts "#{Time.now}: Loading #{__FILE__}"
4
+ STDOUT.sync = true
5
+
6
+ begin
7
+ require 'pry'
8
+ rescue LoadError
9
+ end
10
+
11
+ $: << File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
12
+ require 'logger'
13
+ require 'needle'
14
+ require 'odba/id_server'
15
+ require 'rrba/server'
16
+ require 'ydim/autoinvoicer'
17
+ require 'ydim/client'
18
+ require 'ydim/currency_converter'
19
+ require 'ydim/currency_updater'
20
+ require 'ydim/factory'
21
+ require 'ydim/root_user'
22
+ require 'ydim/server'
23
+ require 'ydim/util'
24
+ require 'odba/18_19_loading_compatibility'
25
+
26
+ require 'odba/connection_pool'
27
+ require 'odba/drbwrapper'
28
+ require 'ydim/odba'
29
+ require 'ydim/root_session'
30
+ require 'ydim/root_user'
31
+
32
+ module YDIM
33
+ class Server
34
+ # http://stackoverflow.com/questions/2982677/ruby-1-9-invalid-byte-sequence-in-utf-8
35
+ # https://robots.thoughtbot.com/fight-back-utf-8-invalid-byte-sequences
36
+ def sanitize_utf8(string)
37
+ return nil if string.nil?
38
+ # return string.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
39
+ return string if string.valid_encoding?
40
+ if string.force_encoding(Encoding::ISO_8859_1).valid_encoding?
41
+ string.force_encoding(Encoding::ISO_8859_1).clone.encode(Encoding::UTF_8)
42
+ else
43
+ string.chars.select { |c| c.valid_encoding? }.join
44
+ end
45
+ end
46
+ def _migrate_to_utf8 queue, table, opts={}
47
+ while obj = queue.shift do
48
+ if obj.is_a?(Numeric)
49
+ begin
50
+ obj = ODBA.cache.fetch obj
51
+ rescue ODBA::OdbaError
52
+ return
53
+ end
54
+ else
55
+ obj = obj.odba_instance
56
+ end
57
+ puts " #{__LINE__}: Migrating #{obj.class} #{obj.to_s}" if $VERBOSE
58
+ return unless obj
59
+ _migrate_obj_to_utf8 obj, queue, table, opts
60
+ obj.odba_store unless obj.odba_unsaved?
61
+ end
62
+ end
63
+ def _migrate_obj_to_utf8 obj, queue, table, opts={}
64
+ obj.instance_variables.each do |name|
65
+ child = obj.instance_variable_get name
66
+ if child.respond_to?(:odba_unsaved?) && !child.odba_unsaved? \
67
+ && obj.respond_to?(:odba_serializables) \
68
+ && obj.odba_serializables.include?(name)
69
+ child.instance_variable_set '@odba_persistent', nil
70
+ end
71
+ child = _migrate_child_to_utf8 child, queue, table, opts
72
+ obj.instance_variable_set name, child
73
+ end
74
+ if obj.is_a?(Array)
75
+ obj.collect! do |child|
76
+ _migrate_child_to_utf8 child, queue, table, opts
77
+ end
78
+ end
79
+ if obj.is_a?(Hash)
80
+ obj.dup.each do |key, child|
81
+ obj.store key, _migrate_child_to_utf8(child, queue, table, opts)
82
+ end
83
+ end
84
+ obj
85
+ end
86
+ def _migrate_child_to_utf8 child, queue, table, opts={}
87
+ @serialized ||= {}
88
+ case child
89
+ when ODBA::Persistable, ODBA::Stub
90
+ if child = child.odba_instance
91
+ if child.odba_unsaved?
92
+ _migrate_to_utf8 [child], table, opts
93
+ elsif opts[:all]
94
+ odba_id = child.odba_id
95
+ unless table[odba_id]
96
+ table.store odba_id, true
97
+ queue.push odba_id
98
+ end
99
+ end
100
+ end
101
+ when String
102
+ old = child.encoding
103
+ if ( child.encoding != Encoding::UTF_8 && child.force_encoding(Encoding::ISO_8859_1).valid_encoding? ) ||
104
+ ( child.encoding == Encoding::UTF_8 && !child.valid_encoding? )
105
+ child = child.force_encoding(Encoding::ISO_8859_1).clone.encode(Encoding::UTF_8)
106
+ puts "force_encoding from ISO_8859_1 #{old}. is now #{child}"
107
+ end
108
+ binding.pry unless child.valid_encoding?
109
+ case child.encoding.to_s
110
+ when /ASCII-8BIT|US-ASCII/
111
+ # nothing todo
112
+ when /UTF-8/
113
+ puts "UTF-8: for #{child.to_s}" if $VERBOSE
114
+ child = sanitize_utf8(child)
115
+ when /ISO-8859-1/i
116
+ child = sanitize_utf8(child)
117
+ # child = child.force_encoding('UTF-8')
118
+ puts "force_encoding from #{old}. is now #{child}"
119
+ else
120
+ puts "Unhandeled encoding #{child.encoding}"
121
+ # require 'pry'; binding.pry
122
+ # child = child.force_encoding
123
+ end
124
+ when
125
+ YDIM::AutoInvoice,
126
+ YDIM::Debitor,
127
+ YDIM::Debitor,
128
+ YDIM::Invoice,
129
+ YDIM::Invoice::Info,
130
+ YDIM::Item
131
+ child = _migrate_obj_to_utf8 child, queue, table, opts
132
+ when Float, Fixnum, TrueClass, FalseClass, NilClass,
133
+ Symbol, Time, Date, DateTime,
134
+ YDIM::Factory,
135
+ YDIM::CurrencyConverter,
136
+ YDIM::MobileCurrencyConverter
137
+ # do nothing
138
+ else
139
+ @ignored ||= {}
140
+ unless @ignored[child.class]
141
+ @ignored.store child.class, true
142
+ warn "ignoring #{child.class}"
143
+ end
144
+ end
145
+ child
146
+ rescue SystemStackError
147
+ puts child.class
148
+ raise
149
+ end
150
+ end
151
+ end
152
+
153
+ puts "#{Time.now}: Calling migrate_to_utf8"
154
+ @logger = Logger.new('migrate_to_utf8.log')
155
+ @logger.level = Logger::DEBUG
156
+ config = YDIM::Server.config
157
+
158
+ ODBA.storage.dbi = ODBA::ConnectionPool.new(config.db_driver_url,
159
+ config.db_user, config.db_auth, :client_encoding => 'LATIN1')
160
+ ODBA.cache.setup
161
+
162
+ DRb.install_id_conv ODBA::DRbIdConv.new
163
+
164
+ if false
165
+ server = YDIM::Server.new(config, logger)
166
+ server.extend(DRbUndumped)
167
+
168
+ puts config.inspect
169
+ if(config.detach)
170
+ pidfile = '/var/run/ydimd.pid'
171
+ File.open(pidfile, 'w') { |fh| fh.puts $$ }
172
+ at_exit { File.unlink(pidfile) }
173
+ Process.fork and exit!(0)
174
+ end
175
+
176
+ begin
177
+ url = config.server_url
178
+ url.untaint
179
+ DRb.start_service(url, server)
180
+ $SAFE = 1
181
+ logger.info('start') {
182
+ sprintf("starting ydim-server on %s", config.server_url) }
183
+ DRb.thread.join
184
+ rescue Exception => error
185
+ logger.error('fatal') { error }
186
+ raise
187
+ end
188
+ end
189
+
190
+ @server = YDIM::Server.new(config, @logger)
191
+ @server.extend(DRbUndumped)
192
+ session = YDIM::RootSession.new(YDIM::RootUser.new(1))
193
+ session.serv = @server
194
+ { :autoinvoices => session.autoinvoices,
195
+ :invoices => session.invoices,
196
+ :debitors => session.debitors,
197
+ }.each do |name, to_migrate|
198
+ puts "#{Time.now}: Start migrating #{name}"
199
+ @server._migrate_to_utf8(to_migrate, {})
200
+ end
201
+ puts "#{Time.now}: Finished migrate_to_utf8"
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  # ypdmd -- ydim -- 09.11.2005 -- hwyss@ywesee.com
3
3
 
4
+ require 'yaml'
5
+ require 'syck'
6
+ require 'pg'
7
+ require 'dbi'
8
+ puts "YAML is now #{YAML} in #{__FILE__} at #{__LINE__}"
9
+
4
10
  require 'ydim/server'
5
11
  require 'ydim/odba'
6
12
  require 'rclconf'
@@ -39,7 +45,7 @@ defaults = {
39
45
  'root_key' => 'root_dsa',
40
46
  'smtp_from' => '',
41
47
  'smtp_server' => 'localhost',
42
- 'vat_rate' => 7.6,
48
+ 'vat_rate' => 8.0,
43
49
  }
44
50
  config = RCLConf::RCLConf.new(ARGV, defaults)
45
51
  config.load(config.config)
@@ -7,7 +7,8 @@ input = if(File.exist?(ARGV.first.to_s))
7
7
  end
8
8
 
9
9
  require 'yaml'
10
-
10
+ require 'syck'
11
+ puts "YAML is now #{YAML} in #{__FILE__} at #{__LINE__}"
11
12
  data = YAML.load(input) || exit(-1)
12
13
 
13
14
  require 'openssl'
data/bin/ydimd CHANGED
@@ -1,7 +1,9 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby231
2
2
  # ypdmd -- ydim -- 12.01.2005 -- hwyss@ywesee.com
3
3
 
4
- require 'fastthread'
4
+ require 'yaml'
5
+ require 'syck'
6
+
5
7
  require 'ydim/server'
6
8
  require 'ydim/odba'
7
9
  require 'logger'
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # Config -- pdfinvoice -- 28.07.2005 -- hwyss@ywesee.com
3
+
4
+ require 'rclconf'
5
+
6
+ module PdfInvoice
7
+ def PdfInvoice.config(argv=[])
8
+ default_dir = File.join(Dir.home, '.pdfinvoice')
9
+ default_config_files = [
10
+ File.join(default_dir, 'config.yml'),
11
+ '/etc/pdfinvoice/config.yml',
12
+ ]
13
+ defaults = {
14
+ 'colors' => {
15
+ 'items' => [0xFA, 0xFA, 0xFA],
16
+ 'total' => [0xF0, 0xF0, 0xF0],
17
+ },
18
+ 'config' => default_config_files,
19
+ 'creditor_address' => "Please set creditor_address etc in: #{default_config_files}",
20
+ 'creditor_email' => '',
21
+ 'creditor_bank' => '',
22
+ 'due_days' => '',
23
+ 'font' => 'Helvetica',
24
+ 'font_b' => 'Helvetica-Bold',
25
+ 'formats' => {
26
+ 'currency' => "%1.2f",
27
+ 'total' => "%1.2f",
28
+ 'date' => "%d.%m.%Y",
29
+ 'invoice_number' => "<b>#%06i</b>",
30
+ 'quantity' => '%1.1f',
31
+ },
32
+ 'logo_path' => nil,
33
+ 'logo_link' => nil,
34
+ 'tax' => 0,
35
+ 'texts' => {
36
+ 'date' => 'Date',
37
+ 'description' => 'Description',
38
+ 'unit' => 'Unit',
39
+ 'quantity' => 'Quantity',
40
+ 'price' => 'Price',
41
+ 'item_total' => 'Item Total',
42
+ 'subtotal' => 'Subtotal',
43
+ 'tax' => 'Tax',
44
+ 'thanks' => nil,
45
+ 'total' => 'Total',
46
+ },
47
+ 'text_options' => {:spacing => 1.25},
48
+ }
49
+ config = RCLConf::RCLConf.new(ARGV, defaults)
50
+ config.load(config.config)
51
+ config
52
+ end
53
+ end
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # Invoice -- pdfinvoice -- 25.07.2005 -- hwyss@ywesee.com
4
+
5
+ begin
6
+ require 'rubygems'
7
+ rescue LoadError
8
+ warn "could not load 'rubygems'"
9
+ end
10
+ require 'pdf/writer'
11
+ require 'pdf/simpletable'
12
+
13
+ module PdfInvoice
14
+ class Invoice
15
+ attr_accessor :invoice_number, :debitor_address, :items, :date, :description
16
+ def initialize(config)
17
+ @config = config
18
+ @date = Date.today
19
+ @items = []
20
+ end
21
+ def to_pdf
22
+ pdf = PDF::Writer.new
23
+ pdf.margins_pt(pdf.mm2pts(15), pdf.mm2pts(25),
24
+ pdf.mm2pts(15), pdf.mm2pts(15))
25
+ pdf.select_font(@config.font)
26
+ pdf_header(pdf)
27
+ pdf_items(pdf)
28
+ pdf_footer(pdf)
29
+ pdf.render
30
+ end
31
+ private
32
+ def calculate_column_widths(pdf, widths, data)
33
+ data.each { |key, text|
34
+ widths[key] = [widths[key], text_width(pdf, text)].max
35
+ }
36
+ widths
37
+ end
38
+ def currency_format(amount, fmt='currency')
39
+ sprintf(@config.formats[fmt], amount)
40
+ end
41
+ def number_format(string)
42
+ string.reverse.gsub(/\d{3}(?=\d)(?!\d*\.)/) do |match|
43
+ match << "'"
44
+ end.reverse
45
+ end
46
+ def pdf_footer(pdf)
47
+ if(txt = @config.texts['thanks'])
48
+ pdf.move_pointer(pdf.font_height)
49
+ pdf.text(txt, @config.text_options)
50
+ end
51
+ end
52
+ def pdf_header(pdf)
53
+ if(path = @config.logo_path)
54
+ args = {:pad => 0}
55
+ if(url = @config.logo_link)
56
+ args.store(:link, url)
57
+ end
58
+ pdf.image(path, args)
59
+ end
60
+ pdf.start_columns(2)
61
+ pdf.text(sprintf(@config.formats['invoice_number'],
62
+ @invoice_number), @config.text_options)
63
+ pdf.text(@description.to_s, @config.text_options)
64
+ pdf.start_new_page
65
+ pdf_lines(pdf, @config.creditor_address)
66
+ pdf.stop_columns
67
+ pdf.start_columns(2)
68
+ pdf_lines(pdf, @debitor_address)
69
+ pdf.start_new_page
70
+ pdf.text(@config.creditor_email, @config.text_options)
71
+ pdf_lines(pdf, @config.creditor_bank)
72
+ pdf.stop_columns
73
+ pdf.move_pointer(pdf.font_height)
74
+ pdf.start_columns(2)
75
+ pdf.text(@config.due_days, @config.text_options)
76
+ pdf.start_new_page
77
+ pdf.text(@date.strftime(@config.formats['date']), @config.text_options)
78
+ pdf.stop_columns
79
+ end
80
+ def pdf_items(pdf)
81
+ sstyle = PDF::Writer::StrokeStyle::DEFAULT.dup
82
+ sstyle.width = 0.5
83
+ sstyle.dash = { :pattern => [1] }
84
+ pdf.start_columns(1)
85
+ pdf.move_pointer(pdf.font_height)
86
+ total = 0.0
87
+ vat = 0.0
88
+ left = 0
89
+ width = 0
90
+ row_gap = 4
91
+ columns = ['date', 'description', 'unit', 'quantity', 'price',
92
+ 'item_total']
93
+ column_widths = {}
94
+ pdf.select_font(@config.font_b)
95
+ headings = columns.collect { |col|
96
+ heading = @config.texts[col]
97
+ column_widths.store(col, text_width(pdf, heading))
98
+ heading
99
+ }
100
+ ['Zwischensumme', 'MwSt 7.5%', 'Fälliger Betrag'].each { |title|
101
+ calculate_column_widths(pdf, column_widths, {'date' => title})
102
+ }
103
+ pdf.select_font(@config.font)
104
+ PDF::SimpleTable.new { |table|
105
+ table.column_order = columns
106
+ columns.each_with_index { |col, idx|
107
+ table.columns[col] = PDF::SimpleTable::Column.new('date') {
108
+ |column|
109
+ column.heading = PDF::SimpleTable::Column::Heading.new(col)
110
+ column.heading.title = headings.at(idx)
111
+ if(['quantity', 'price', 'item_total'].include?(col))
112
+ column.justification = :right
113
+ column.heading.justification = :right
114
+ end
115
+ }
116
+ }
117
+ table.data = @items.collect { |line|
118
+ item_total = line.at(3).to_f * line.at(4).to_f
119
+ vat += line[5] || @config.tax.to_f * item_total
120
+ total += item_total
121
+ date = line.at(0).strftime(@config.formats['date'])
122
+ cw = pdf.text_width(date) * PDF::SimpleTable::WIDTH_FACTOR
123
+ data = {
124
+ 'date' => date,
125
+ 'description' => number_format(line.at(1)),
126
+ 'unit' => line.at(2),
127
+ 'quantity' => number_format(sprintf(@config.formats['quantity'],
128
+ line.at(3))),
129
+ 'price' => number_format(currency_format(line.at(4))),
130
+ 'item_total' => number_format(currency_format(item_total)),
131
+ }
132
+ calculate_column_widths(pdf, column_widths, data)
133
+ data
134
+ }
135
+ column_widths.each { |key, col_width|
136
+ unless(key == 'description')
137
+ table.columns[key].width = col_width + 2 * table.column_gap
138
+ end
139
+ }
140
+ table.position = left = pdf.left_margin + table.column_gap
141
+ table.orientation = :right
142
+ table.width = width = pdf.margin_width - 2 * table.column_gap
143
+ cl = Color::RGB.new(*@config.colors['items'])
144
+ table.shade_color2 = table.shade_color = cl
145
+ table.shade_rows = :striped
146
+ table.show_lines = :all
147
+ table.inner_line_style = sstyle
148
+ table.outer_line_style = sstyle
149
+ table.row_gap = row_gap
150
+ table.bold_headings = true
151
+ table.heading_font_size = 10
152
+ table.render_on(pdf)
153
+ }
154
+ pdf.select_font(@config.font_b)
155
+ PDF::SimpleTable.new { |table|
156
+ table.column_order = ['date', 'total']
157
+ table.data = [
158
+ { 'date' => @config.texts['subtotal'],
159
+ 'total' => number_format(currency_format(total, 'total'))},
160
+ { 'date' => @config.texts['tax'],
161
+ 'total' => number_format(currency_format(vat, 'total')) },
162
+ { 'date' => @config.texts['total'],
163
+ 'total' => number_format(currency_format(total + vat, 'total')) },
164
+ ]
165
+ table.show_headings = false
166
+ table.position = left
167
+ table.orientation = :right
168
+ table.width = width
169
+ table.columns['date'] = PDF::SimpleTable::Column.new('date') {
170
+ |column|
171
+ column.width = column_widths['date'] + 2 * table.column_gap
172
+ }
173
+ table.columns['total'] = PDF::SimpleTable::Column.new('date') {
174
+ |column|
175
+ column.justification = :right
176
+ }
177
+ cl = Color::RGB.new(*@config.colors['total'])
178
+ table.shade_color2 = table.shade_color = cl
179
+ table.shade_rows = :striped
180
+ table.show_lines = :all
181
+ table.inner_line_style = sstyle
182
+ table.outer_line_style = sstyle
183
+ table.row_gap = row_gap
184
+ table.render_on(pdf)
185
+ }
186
+ pdf.select_font(@config.font)
187
+ end
188
+ def pdf_lines(pdf, lines)
189
+ if (lines && lines.is_a?(Array))
190
+ lines.each { |line|
191
+ pdf.text(line.strip, @config.text_options)
192
+ }
193
+ elsif (lines && lines.is_a?(String))
194
+ lines.each_line { |line|
195
+ pdf.text(line.strip, @config.text_options)
196
+ }
197
+ end
198
+ end
199
+ def text_width(pdf, text)
200
+ pdf.text_width(text) * PDF::SimpleTable::WIDTH_FACTOR
201
+ end
202
+ end
203
+ end