knjrbfw 0.0.115 → 0.0.116

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a5be1f9cec88387a3f22d11d086d874af8e86cea2f7e24596957b6ac8bede40
4
- data.tar.gz: '01838b41b901833125a7e13ebbf294c0cc084327e6c878a9052d923ecf8af661'
3
+ metadata.gz: ba4a0d2d304a3aca02bed13395250ec82ece3fef1fb3239a60199615a0310d6c
4
+ data.tar.gz: 4018da1c46f54b99db3c4f9dc6b25e9065c5c64f5de1082006a2db774ad9b3c8
5
5
  SHA512:
6
- metadata.gz: c07835d6c2365d107aeb2bf8ce0d57879069b1cadbd3c97035ef11cd2585de4787e5a82974a48a5901f1cb620f267d6e39bf6c22c042161a88db1aecbe5e4ff1
7
- data.tar.gz: c8f5d705a4cf5e46906fa52aa4ce615755f12cb83eb4e5414e571756fd972fca61ce61ede18a6ec8c550ec747db5fa5df0b6ad58c6ed9e0e56cd94e4ca97f803
6
+ metadata.gz: 2b539bf8e66dc55b6480dee125ddd3578e2fde2543ec7ceac2410611e125e577281599a9473b6c98f7ec8b9815cf660aaf606e5f4a6e52e2470cc8db723701a4
7
+ data.tar.gz: 420e34ac692a52dfb8f401f1bbb40c056613802aa9f1f5fb50b4b58712b80d5562b424661e398d0113706970441bad52ea6c2f014cfa52c9b547b0bdcd9685db
@@ -4,7 +4,7 @@ class ERuby
4
4
  @inseq_cache = false
5
5
  @inseq_rbc = false
6
6
  @java_compile = false
7
-
7
+
8
8
  if RUBY_PLATFORM == "java"
9
9
  @java_compile = true
10
10
  @java_factory = javax.script.ScriptEngineManager.new
@@ -13,51 +13,51 @@ class ERuby
13
13
  elsif RUBY_VERSION.slice(0..2) == "1.9" and RubyVM::InstructionSequence.respond_to?(:compile_file)
14
14
  @eruby_rbyte = {}
15
15
  @inseq_cache = true
16
-
16
+
17
17
  if RubyVM::InstructionSequence.respond_to?(:load)
18
18
  @inseq_rbc = true
19
19
  end
20
20
  end
21
-
21
+
22
22
  @inseq_rbc = false #this is not possible yet in Ruby... maybe in 1.9.3?
23
23
  end
24
-
24
+
25
25
  def self.import(filename)
26
26
  ERuby.load_settings if !@settings_loaded
27
-
27
+
28
28
  filename = File.expand_path(filename)
29
29
  pwd = Dir.pwd
30
30
  Dir.chdir(File.dirname(filename))
31
-
31
+
32
32
  fpath = "#{KnjEruby.filepath}/cache/#{filename.gsub("/", "_").gsub(".", "_")}"
33
33
  pi = Php4r.pathinfo(filename)
34
34
  cachename = fpath + ".cache"
35
-
35
+
36
36
  filetime = File.mtime(filename)
37
- cacheexists = File.exists?(cachename)
38
- cachetime = File.mtime(cachename) if File.exists?(cachename)
39
-
37
+ cacheexists = File.exist?(cachename)
38
+ cachetime = File.mtime(cachename) if File.exist?(cachename)
39
+
40
40
  if !cacheexists or filetime > cachetime
41
41
  KnjEruby.load_file(File.basename(filename), {:cachename => cachename})
42
42
  cachetime = File.mtime(cachename)
43
43
  reload_cache = true
44
44
  end
45
-
45
+
46
46
  if @java_compile
47
47
  if !@eruby_java_cache[cachename] or reload_cache
48
48
  #@eruby_java_cache[cachename] = @java_engine.compile(File.read(cachename))
49
49
  @eruby_java_cache[cachename] = File.read(cachename)
50
50
  end
51
-
51
+
52
52
  #@eruby_java_cache[cachename].eval
53
53
  eval(@eruby_java_cache[cachename])
54
54
  elsif @inseq_cache
55
55
  if @inseq_rbc
56
56
  bytepath = pi["dirname"] + "/" + pi["basename"] + ".rbc"
57
- byteexists = File.exists?(bytepath)
58
- bytetime = File.mtime(bytepath) if File.exists?(bytepath)
59
-
60
- if !File.exists?(bytepath) or cachetime > bytetime
57
+ byteexists = File.exist?(bytepath)
58
+ bytetime = File.mtime(bytepath) if File.exist?(bytepath)
59
+
60
+ if !File.exist?(bytepath) or cachetime > bytetime
61
61
  res = RubyVM::InstructionSequence.compile_file(filename)
62
62
  data = Marshal.dump(res.to_a)
63
63
  File.open(bytepath, "w+") do |fp|
@@ -65,7 +65,7 @@ class ERuby
65
65
  end
66
66
  end
67
67
  end
68
-
68
+
69
69
  if @inseq_rbc
70
70
  res = Marshal.load(File.read(bytepath))
71
71
  RubyVM::InstructionSequence.load(res).eval
@@ -86,70 +86,70 @@ class ERuby
86
86
  eruby = KnjEruby.load_file(File.basename(filename), {:cachename => cachename})
87
87
  print eruby.evaluate
88
88
  end
89
-
89
+
90
90
  Dir.chdir(pwd)
91
91
  end
92
92
  end
93
93
 
94
94
  class KnjEruby < Erubis::Eruby
95
95
  include Erubis::StdoutEnhancer
96
-
96
+
97
97
  @headers = [
98
98
  ["Content-Type", "text/html; charset=utf-8"]
99
99
  ]
100
100
  @filepath = File.dirname(Knj::Os::realpath(__FILE__))
101
101
  @connects = {}
102
-
102
+
103
103
  def self.fcgi=(newvalue); @fcgi = newvalue; end
104
104
  def self.fcgi; return @fcgi; end
105
105
  def self.connects; return @connects; end
106
106
  def self.headers; return @headers; end
107
-
107
+
108
108
  def self.print_headers(args = {})
109
109
  header_str = ""
110
-
110
+
111
111
  @headers.each do |header|
112
112
  header_str << "#{header[0]}: #{header[1]}\n"
113
113
  end
114
-
114
+
115
115
  header_str << "\n"
116
116
  self.reset_headers if @fcgi
117
117
  return header_str
118
118
  end
119
-
119
+
120
120
  def self.has_status_header?
121
121
  @headers.each do |header|
122
122
  if header[0] == "Status"
123
123
  return true
124
124
  end
125
125
  end
126
-
126
+
127
127
  return false
128
128
  end
129
-
129
+
130
130
  def self.reset_connects
131
131
  @connects = {}
132
132
  end
133
-
133
+
134
134
  def self.reset_headers
135
135
  @headers = [
136
136
  ["Content-Type", "text/html; charset=utf-8"]
137
137
  ]
138
138
  end
139
-
139
+
140
140
  def self.header(key, value)
141
141
  @headers << [key, value]
142
142
  end
143
-
143
+
144
144
  def self.filepath
145
145
  return @filepath
146
146
  end
147
-
147
+
148
148
  def self.connect(signal, &block)
149
149
  @connects[signal] = [] if !@connects[signal]
150
150
  @connects[signal] << block
151
151
  end
152
-
152
+
153
153
  def self.printcont(tmp_out, args = {})
154
154
  if @fcgi
155
155
  @fcgi.print self.print_headers
@@ -162,7 +162,7 @@ class KnjEruby < Erubis::Eruby
162
162
  elsif !args[:custom_io]
163
163
  $stdout = STDOUT
164
164
  end
165
-
165
+
166
166
  if !args[:custom_io]
167
167
  print self.print_headers if !args.key?(:with_headers) or args[:with_headers]
168
168
  tmp_out.rewind
@@ -170,37 +170,37 @@ class KnjEruby < Erubis::Eruby
170
170
  end
171
171
  end
172
172
  end
173
-
173
+
174
174
  def self.load_return(filename, args = {})
175
175
  if !args[:io]
176
176
  retio = StringIO.new
177
177
  args[:io] = retio
178
178
  end
179
-
179
+
180
180
  @args = args
181
181
  KnjEruby.load(filename, args)
182
-
182
+
183
183
  if !args[:custom_io]
184
184
  retio.rewind
185
185
  return retio.read
186
186
  end
187
187
  end
188
-
188
+
189
189
  def self.load(filename, args = {})
190
190
  begin
191
191
  if !args[:custom_io]
192
192
  tmp_out = StringIO.new
193
193
  $stdout = tmp_out
194
194
  end
195
-
195
+
196
196
  ERuby.import(filename)
197
-
197
+
198
198
  if KnjEruby.connects["exit"]
199
199
  KnjEruby.connects["exit"].each do |block|
200
200
  block.call
201
201
  end
202
202
  end
203
-
203
+
204
204
  KnjEruby.printcont(tmp_out, args)
205
205
  rescue SystemExit => e
206
206
  KnjEruby.printcont(tmp_out, args)
@@ -217,31 +217,31 @@ class KnjEruby < Erubis::Eruby
217
217
  #An error occurred while trying to run the on-error-block - show this as an normal error.
218
218
  print "\n\n<pre>\n\n"
219
219
  print "<b>#{Knj::Web.html(e.class.name)}: #{Knj::Web.html(e.message)}</b>\n\n"
220
-
220
+
221
221
  #Lets hide all the stuff in what is not the users files to make it easier to debug.
222
222
  bt = e.backtrace
223
223
  #to = bt.length - 9
224
224
  #bt = bt[0..to]
225
-
225
+
226
226
  bt.each do |line|
227
227
  print Knj::Web.html(line) + "\n"
228
228
  end
229
-
229
+
230
230
  print "</pre>"
231
231
  end
232
-
232
+
233
233
  print "\n\n<pre>\n\n"
234
234
  print "<b>#{Knj::Web.html(e.class.name)}: #{Knj::Web.html(e.message)}</b>\n\n"
235
-
235
+
236
236
  #Lets hide all the stuff in what is not the users files to make it easier to debug.
237
237
  bt = e.backtrace
238
238
  to = bt.length - 9
239
239
  bt = bt[0..to]
240
-
240
+
241
241
  bt.each do |line|
242
242
  print Knj::Web.html(line) + "\n"
243
243
  end
244
-
244
+
245
245
  KnjEruby.printcont(tmp_out, args)
246
246
  end
247
247
  end
data/lib/knj/eruby.rb CHANGED
@@ -1,25 +1,25 @@
1
1
  #Uses Rubinius, Knj::Compiler, RubyVM::InstructionSequence and eval to convert and execute .rhtml-files.
2
2
  class Knj::Eruby
3
3
  attr_reader :connects, :error, :headers, :cookies, :fcgi
4
-
4
+
5
5
  #Sets various arguments and prepares for parsing.
6
6
  def initialize(args = {})
7
7
  @args = args
8
-
8
+
9
9
  @tmpdir = "#{Knj::Os.tmpdir}/knj_erb"
10
- if !File.exists?(@tmpdir)
10
+ if !File.exist?(@tmpdir)
11
11
  Dir.mkdir(@tmpdir, 0777)
12
12
  File.chmod(0777, @tmpdir)
13
13
  end
14
-
15
-
14
+
15
+
16
16
  #This argument can be used if a shared cache should be used to speed up performance.
17
17
  if @args[:cache_hash]
18
18
  @cache = @args[:cache_hash]
19
19
  else
20
20
  @cache = {}
21
21
  end
22
-
22
+
23
23
  if RUBY_PLATFORM == "java" or RUBY_ENGINE == "rbx"
24
24
  @cache_mode = :code_eval
25
25
  #@cache_mode = :compile_knj
@@ -28,35 +28,35 @@ class Knj::Eruby
28
28
  #@cache_mode = :inseq
29
29
  #@cache_mode = :compile_knj
30
30
  end
31
-
31
+
32
32
  if @cache_mode == :compile_knj
33
33
  require "#{$knjpath}compiler"
34
34
  @compiler = Knj::Compiler.new(:cache_hash => @cache)
35
35
  end
36
-
36
+
37
37
  self.reset_headers
38
38
  self.reset_connects
39
39
  end
40
-
40
+
41
41
  #Imports and evaluates a new .rhtml-file.
42
42
  #===Examples
43
43
  # erb.import("/path/to/some_file.rhtml")
44
44
  def import(filename)
45
45
  @error = false
46
- Dir.mkdir(@tmpdir) if !File.exists?(@tmpdir)
46
+ Dir.mkdir(@tmpdir) if !File.exist?(@tmpdir)
47
47
  filename = File.expand_path(filename)
48
- raise "File does not exist: #{filename}" unless File.exists?(filename)
48
+ raise "File does not exist: #{filename}" unless File.exist?(filename)
49
49
  cachename = "#{@tmpdir}/#{filename.gsub("/", "_").gsub(".", "_")}.cache"
50
50
  filetime = File.mtime(filename)
51
- cachetime = File.mtime(cachename) if File.exists?(cachename)
52
-
53
- if !File.exists?(cachename) or filetime > cachetime
51
+ cachetime = File.mtime(cachename) if File.exist?(cachename)
52
+
53
+ if !File.exist?(cachename) or filetime > cachetime
54
54
  Knj::Eruby::Handler.load_file(filename, :cachename => cachename)
55
55
  File.chmod(0777, cachename)
56
56
  cachetime = File.mtime(cachename)
57
57
  reload_cache = true
58
58
  end
59
-
59
+
60
60
  begin
61
61
  case @cache_mode
62
62
  when :compile_knj
@@ -68,19 +68,19 @@ class Knj::Eruby
68
68
  eruby_binding = Knj::Eruby::Binding.new
69
69
  binding_use = eruby_binding.get_binding
70
70
  end
71
-
71
+
72
72
  #No reason to cache contents of files - benchmarking showed little to no differene performance-wise, but caching took up a lot of memory, when a lot of files were cached - knj.
73
73
  eval(File.read(cachename), binding_use, filename)
74
74
  when :inseq
75
75
  reload_cache = true if !@cache.key?(cachename)
76
-
76
+
77
77
  if reload_cache or @cache[cachename][:time] < cachetime
78
78
  @cache[cachename] = {
79
79
  :inseq => RubyVM::InstructionSequence.compile(File.read(cachename), filename, nil, 1),
80
80
  :time => Time.now
81
81
  }
82
82
  end
83
-
83
+
84
84
  @cache[cachename][:inseq].eval
85
85
  else
86
86
  loaded_content = Knj::Eruby::Handler.load_file(filename, {:cachename => cachename})
@@ -94,16 +94,16 @@ class Knj::Eruby
94
94
  @error = true
95
95
  self.handle_error(e)
96
96
  end
97
-
97
+
98
98
  return nil
99
99
  end
100
-
100
+
101
101
  #Destroyes this object unsetting all variables and clearing all cache.
102
102
  def destroy
103
103
  @connects.clear if @connects.is_a?(Hash)
104
104
  @headers.clear if @headers.is_a?(Array)
105
105
  @cookies.clear if @cookies.is_a?(Array)
106
-
106
+
107
107
  @cache.clear if @cache.is_a?(Hash) and @args and !@args.key?(:cache_hash)
108
108
  @args.clear if @args.is_a?(Hash)
109
109
  @args = nil
@@ -111,68 +111,68 @@ class Knj::Eruby
111
111
  @connects = nil
112
112
  @headers = nil
113
113
  @cookies = nil
114
-
114
+
115
115
  return nil
116
116
  end
117
-
117
+
118
118
  #Returns various headers as one complete string ready to be used in a HTTP-request.
119
119
  def print_headers(args = {})
120
120
  header_str = ""
121
-
121
+
122
122
  @headers.each do |header|
123
123
  header_str << "#{header[0]}: #{header[1]}\n"
124
124
  end
125
-
125
+
126
126
  @cookies.each do |cookie|
127
127
  header_str << "Set-Cookie: #{Knj::Web.cookie_str(cookie)}\n"
128
128
  end
129
-
129
+
130
130
  header_str << "\n"
131
131
  self.reset_headers if @fcgi
132
132
  return header_str
133
133
  end
134
-
134
+
135
135
  #Returns true if containing a status-header.
136
136
  def has_status_header?
137
137
  @headers.each do |header|
138
138
  return true if header[0] == "Status"
139
139
  end
140
-
140
+
141
141
  return false
142
142
  end
143
-
143
+
144
144
  #Resets all connections.
145
145
  def reset_connects
146
146
  @connects = {}
147
147
  return nil
148
148
  end
149
-
149
+
150
150
  #Resets all headers.
151
151
  def reset_headers
152
152
  @headers = []
153
153
  @cookies = []
154
154
  return nil
155
155
  end
156
-
156
+
157
157
  #Adds a new header to the list.
158
158
  def header(key, value)
159
159
  @headers << [key, value]
160
160
  return nil
161
161
  end
162
-
162
+
163
163
  #Adds a new cookie to the list.
164
164
  def cookie(cookie_data)
165
165
  @cookies << cookie_data
166
166
  return nil
167
167
  end
168
-
168
+
169
169
  #Connects a block to a certain event.
170
170
  def connect(signal, &block)
171
171
  @connects[signal] = [] if !@connects.key?(signal)
172
172
  @connects[signal] << block
173
173
  return nil
174
174
  end
175
-
175
+
176
176
  def printcont(tmp_out, args = {})
177
177
  if @fcgi
178
178
  @fcgi.print self.print_headers
@@ -185,46 +185,46 @@ class Knj::Eruby
185
185
  elsif !args[:custom_io]
186
186
  $stdout = STDOUT
187
187
  end
188
-
188
+
189
189
  if !args[:custom_io]
190
190
  print self.print_headers if !args.key?(:with_headers) or args[:with_headers]
191
191
  tmp_out.rewind
192
192
  print tmp_out.read
193
193
  end
194
194
  end
195
-
195
+
196
196
  return nil
197
197
  end
198
-
198
+
199
199
  def load_return(filename, args = {})
200
200
  if !args[:io]
201
201
  retio = StringIO.new
202
202
  args[:io] = retio
203
203
  end
204
-
204
+
205
205
  self.load_filename(filename, args)
206
-
206
+
207
207
  if !args[:custom_io]
208
208
  retio.rewind
209
209
  return retio.read
210
210
  end
211
211
  end
212
-
212
+
213
213
  def load_filename(filename, args = {})
214
214
  begin
215
215
  if !args[:custom_io]
216
216
  tmp_out = StringIO.new
217
217
  $stdout = tmp_out
218
218
  end
219
-
219
+
220
220
  self.import(filename)
221
-
221
+
222
222
  if @connects["exit"]
223
223
  @connects["exit"].each do |block|
224
224
  block.call
225
225
  end
226
226
  end
227
-
227
+
228
228
  self.printcont(tmp_out, args)
229
229
  rescue SystemExit => e
230
230
  self.printcont(tmp_out, args)
@@ -232,10 +232,10 @@ class Knj::Eruby
232
232
  self.handle_error(e)
233
233
  self.printcont(tmp_out, args)
234
234
  end
235
-
235
+
236
236
  return nil
237
237
  end
238
-
238
+
239
239
  #This method will handle an error without crashing simply adding the error to the print-queue.
240
240
  def handle_error(e)
241
241
  begin
@@ -250,23 +250,23 @@ class Knj::Eruby
250
250
  #An error occurred while trying to run the on-error-block - show this as an normal error.
251
251
  print "\n\n<pre>\n\n"
252
252
  print "<b>#{Knj::Web.html(e.class.name)}: #{Knj::Web.html(e.message)}</b>\n\n"
253
-
253
+
254
254
  e.backtrace.each do |line|
255
255
  print "#{Knj::Web.html(line)}\n"
256
256
  end
257
-
257
+
258
258
  print "</pre>"
259
259
  end
260
-
260
+
261
261
  print "\n\n<pre>\n\n"
262
262
  print "<b>#{Knj::Web.html(e.class.name)}: #{Knj::Web.html(e.message)}</b>\n\n"
263
-
263
+
264
264
  e.backtrace.each do |line|
265
265
  print "#{Knj::Web.html(line)}\n"
266
266
  end
267
-
267
+
268
268
  print "</pre>"
269
-
269
+
270
270
  return nil
271
271
  end
272
272
  end
@@ -1,6 +1,6 @@
1
1
  class Knj::Filesystem
2
2
  def self.copy(args)
3
- FileUtils.rm(args[:to]) if args[:replace] and File.exists?(args[:to])
3
+ FileUtils.rm(args[:to]) if args[:replace] and File.exist?(args[:to])
4
4
  FileUtils.cp(args[:from], args[:to])
5
5
  mod = File.lstat(args[:from]).mode & 0777
6
6
  File.chmod(mod, args[:to])
@@ -2,10 +2,10 @@
2
2
  class Knj::Gettext_threadded
3
3
  #Hash that contains all translations loaded.
4
4
  attr_reader :langs
5
-
5
+
6
6
  #Config-hash that contains encoding and more.
7
7
  attr_reader :args
8
-
8
+
9
9
  #Initializes various data.
10
10
  def initialize(args = {})
11
11
  @args = {
@@ -15,32 +15,32 @@ class Knj::Gettext_threadded
15
15
  @dirs = []
16
16
  load_dir(@args["dir"]) if @args["dir"]
17
17
  end
18
-
18
+
19
19
  #Loads a 'locales'-directory with .mo- and .po-files and fills the '@langs'-hash.
20
20
  #===Examples
21
21
  # gtext.load_dir("#{File.dirname(__FILE__)}/../locales")
22
22
  def load_dir(dir)
23
23
  @dirs << dir
24
24
  check_folders = ["LC_MESSAGES", "LC_ALL"]
25
-
25
+
26
26
  Dir.foreach(dir) do |file|
27
27
  fn = "#{dir}/#{file}"
28
28
  if File.directory?(fn) and file.match(/^[a-z]{2}_[A-Z]{2}$/)
29
29
  @langs[file] = {} if !@langs[file]
30
-
30
+
31
31
  check_folders.each do |fname|
32
32
  fpath = "#{dir}/#{file}/#{fname}"
33
-
34
- if File.exists?(fpath) and File.directory?(fpath)
33
+
34
+ if File.exist?(fpath) and File.directory?(fpath)
35
35
  Dir.foreach(fpath) do |pofile|
36
36
  if pofile.match(/\.po$/)
37
37
  pofn = "#{dir}/#{file}/#{fname}/#{pofile}"
38
-
38
+
39
39
  cont = nil
40
40
  File.open(pofn, "r", {:encoding => @args[:encoding]}) do |fp|
41
41
  cont = fp.read.encode("utf-8")
42
42
  end
43
-
43
+
44
44
  cont.scan(/msgid\s+\"(.+)\"(\r|)\nmsgstr\s+\"(.+)\"(\r|)\n(\r|)\n/) do |match|
45
45
  @langs[file][match[0]] = match[2].to_s.encode("utf-8")
46
46
  end
@@ -51,7 +51,7 @@ class Knj::Gettext_threadded
51
51
  end
52
52
  end
53
53
  end
54
-
54
+
55
55
  #Translates a given string to a given locale from the read .po-files.
56
56
  #===Examples
57
57
  # str = "Hello" #=> "Hello"
@@ -59,40 +59,40 @@ class Knj::Gettext_threadded
59
59
  def trans(locale, str)
60
60
  locale = locale.to_s
61
61
  str = str.to_s
62
-
62
+
63
63
  if !@langs.key?(locale)
64
64
  raise "Locale was not found: '#{locale}' in '#{@langs.keys.join(", ")}'."
65
65
  end
66
-
66
+
67
67
  return str if !@langs[locale].key?(str)
68
68
  return @langs[locale][str]
69
69
  end
70
-
70
+
71
71
  #This function can be used to make your string be recognized by gettext tools.
72
72
  def gettext(str, locale)
73
73
  return trans(locale, str)
74
74
  end
75
-
75
+
76
76
  #Returns a hash with the language ID string as key and the language human-readable-title as value.
77
77
  def lang_opts
78
78
  langs = {}
79
79
  @langs.keys.sort.each do |lang|
80
80
  title = nil
81
-
81
+
82
82
  @dirs.each do |dir|
83
83
  title_file_path = "#{dir}/#{lang}/title.txt"
84
- if File.exists?(title_file_path)
84
+ if File.exist?(title_file_path)
85
85
  title = File.read(title_file_path, {:encoding => @args[:encoding]}).to_s.strip
86
86
  else
87
87
  title = lang.to_s.strip
88
88
  end
89
-
89
+
90
90
  break if title
91
91
  end
92
-
92
+
93
93
  langs[lang] = title
94
94
  end
95
-
95
+
96
96
  return langs
97
97
  end
98
98
  end