Merge: Follow the INI specification
authorJean Privat <jean@pryen.org>
Wed, 3 Jul 2019 18:50:48 +0000 (14:50 -0400)
committerJean Privat <jean@pryen.org>
Wed, 3 Jul 2019 18:50:48 +0000 (14:50 -0400)
This version follows more closely the INI specification (https://en.wikipedia.org/wiki/INI_file) and adds some improvements to the API.

Spec changes:
* Allow `#` and `;` for comments
* No more sections nesting as by the spec (which actually change nothing see below)

API changes:
* Renaming `ConfigTree` -> `IniFile` (as it's no more a "tree")
* No more coupling with a file path, use utility methods `load_string`, `load_file`, `write_to`  instead
* Ability to iterate keys, values, sections and section content
* Ability to create `IniSection` by hand
* `IniFile` and `IniSection` implements `Map[String, nullable String]`

The biggest change is that sub-sections are now flattened.

Before, with the following ini:

~~~ini
[section1]
key1=value1
[section1.section2]
key2=value2
~~~

You would get this hierarchy:

~~~
section1
  |   key1=value1
  `- section2
      `-  key2=value2
~~~

Now you get:

~~~
section1
  `- key1=value1
section1.section2
  `-  key2=value2
~~~

Two independent (unnested) sections, one called `section1` and the other called `section1.section2`.
This actually change nothing if the client was using the `[]` operator with the `.` notation as:

~~~
ini["section1.section2.key2"] # still returns `value2`
~~~

Documentation and specification from the README:

# `ini` - Read and write INI configuration files

[INI files](https://en.wikipedia.org/wiki/INI_file) are simple text files with
a basic structure composed of sections, properties and values used to store
configuration parameters.

Here's an example from the `package.ini` of this package:

~~~nit
import ini

var package_ini = """
[package]
name=ini
desc=Read and write INI configuration files.
[upstream]
git=https://github.com/nitlang/nit.git
git.directory=lib/ini/
"""
~~~

## Basic usage

`IniFile` is used to parse INI strings and access their content:

~~~nit
var ini = new IniFile.from_string(package_ini)
assert ini["package.name"] == "ini"
assert ini["upstream.git.directory"] == "lib/ini/"
assert ini["unknown.unknown"] == null
~~~

`IniFile` can also load INI configuration from a file:

~~~nit
package_ini.write_to_file("my_package.ini")

ini = new IniFile.from_file("my_package.ini")
assert ini["package.name"] == "ini"
assert ini["upstream.git.directory"] == "lib/ini/"

"my_package.ini".to_path.delete
~~~

INI content can be added or edited through the `IniFile` API then written to
a stream or a file.

~~~nit
ini["package.name"] = "new name"
ini["upstream.git.directory"] = "/dev/null"
ini["section.key"] = "value"

var stream = new StringWriter
ini.write_to(stream)

assert stream.to_s == """
[package]
name=new name
desc=Read and write INI configuration files.
[upstream]
git=https://github.com/nitlang/nit.git
git.directory=/dev/null
[section]
key=value
"""
~~~

## INI content

### Properties

Properties are the basic element of the INI format.
Every property correspond to a *key* associated to a *value* thanks to the equal (`=`) sign.

~~~nit
ini = new IniFile.from_string("""
key1=value1
key2=value2
""")
assert ini["key1"] == "value1"
assert ini["key2"] == "value2"
assert ini.length == 2
~~~

Accessing an unknown property returns `null`:

~~~nit
assert ini["unknown"] == null
~~~

Properties can be iterated over:

~~~nit
var i = 1
for key, value in ini do
assert key == "key{i}"
assert value == "value{i}"
i += 1
end
~~~

Property keys cannot contain the character `=`.
Values can contain any character.
Spaces are trimmed.

~~~nit
ini = new IniFile.from_string("""
prop=erty1=value1
 property2 =  value2
property3=value3 ; with semicolon
""")
assert ini[";property1"] == null
assert ini["prop=erty1"] == null
assert ini["prop"] == "erty1=value1"
assert ini["property2"] == "value2"
assert ini[" property2 "] == "value2"
assert ini["property3"] == "value3 ; with semicolon"
~~~

Both keys and values are case sensitive.

~~~nit
ini = new IniFile.from_string("""
Property1=value1
property2=Value2
""")
assert ini["property1"] == null
assert ini["Property1"] == "value1"
assert ini["property2"] != "value2"
assert ini["property2"] == "Value2"
~~~

### Sections

Properties may be grouped into arbitrary sections.
The section name appears on a line by itself between square brackets (`[` and `]`).

All keys after the section declaration are associated with that section.
The is no explicit "end of section" delimiter; sections end at the next section
declaration or the end of the file.
Sections cannot be nested.

~~~nit
var content = """
key1=value1
key2=value2
[section1]
key1=value3
key2=value4
[section2]
key1=value5
"""

ini = new IniFile.from_string(content)
assert ini["key1"] == "value1"
assert ini["unknown"] == null
assert ini["section1.key1"] == "value3"
assert ini["section1.unknown"] == null
assert ini["section2.key1"] == "value5"
~~~

Sections can be iterated over:

~~~nit
i = 1
for section in ini.sections do
assert section.name == "section{i}"
assert section["key1"].has_prefix("value")
i += 1
end
~~~

When iterating over a file properties, only properties at root are returned.
`flatten` can be used to iterate over all properties including the one from
sections.

~~~nit
assert ini.join(", ", ": ") == "key1: value1, key2: value2"
assert ini.flatten.join(", ", ": ") ==
"key1: value1, key2: value2, section1.key1: value3, section1.key2: value4, section2.key1: value5"

i = 0
for key, value in ini do
i += 1
assert key == "key{i}" and value == "value{i}"
end
assert i == 2

~~~

Sections name may contain any character including brackets (`[` and `]`).
Spaces are trimmed.

~~~nit
ini = new IniFile.from_string("""
[[section1]]
key=value1
[ section 2 ]
key=value2
[section1.section3]
key=value3
""")
assert ini.sections.length == 3
assert ini["[section1].key"] == "value1"
assert ini["section 2.key"] == "value2"
assert ini["section1.section3.key"] == "value3"
assert ini.sections.last.name == "section1.section3"
~~~

The dot `.` notation is used to create new sections with `[]=`.
Unknown sections will be created on the fly.

~~~nit
ini = new IniFile
ini["key"] = "value1"
ini["section1.key"] = "value2"
ini["section2.key"] = "value3"

stream = new StringWriter
ini.write_to(stream)
assert stream.to_s == """
key=value1
[section1]
key=value2
[section2]
key=value3
"""
~~~

Sections can also be created manually:

~~~nit
ini = new IniFile
ini["key"] = "value1"

var section = new IniSection("section1")
section["key"] = "value2"
ini.sections.add section

stream = new StringWriter
ini.write_to(stream)
assert stream.to_s == """
key=value1
[section1]
key=value2
"""
~~~

### Comments

Comments are indicated by semicolon (`;`) or a number sign (`#`) at the begining
of the line. Commented lines are ignored as well as empty lines.

~~~nit
ini = new IniFile.from_string("""
; This is a comment.
; property1=value1

# This is another comment.
# property2=value2
""")
assert ini.is_empty
~~~

### Unicode support

INI files support Unicode:

~~~nit
ini = new IniFile.from_string("""
property❤=héhé
""")
assert ini["property❤"] == "héhé"
~~~

Pull-Request: #2752
Reviewed-by: Jean Privat <jean@pryen.org>

24 files changed:
.gitlab-ci.yml
lib/core/bytes.nit
lib/core/collection/abstract_collection.nit
lib/core/stream.nit
lib/github/loader.nit
lib/logger/logger.nit [new file with mode: 0644]
lib/logger/package.ini [new file with mode: 0644]
lib/popcorn/README.md
lib/popcorn/pop_logging.nit
lib/popcorn/pop_tracker.nit
lib/pthreads/concurrent_collections.nit
misc/docker/ci/Dockerfile
src/nitunit.nit
src/nitweb.nit
tests/sav/nitunit_args1.res
tests/sav/nitunit_args10.res
tests/sav/nitunit_args11.res
tests/sav/nitunit_args12.res
tests/sav/nitunit_args13.res
tests/sav/nitunit_args14.res
tests/sav/nitunit_args9.res
tests/sav/test_file_read3.res [new file with mode: 0644]
tests/test_file_read3.nit [new file with mode: 0644]
tests/tests.sh

index a0711b3..70b91e1 100644 (file)
@@ -77,7 +77,7 @@ test_some: &test_some
   artifacts:
     paths:
       - tests/errlist
-      - tests/*.xml
+      - tests/*.xml*
     when: always
     reports:
       junit: tests/*.xml
@@ -90,6 +90,7 @@ nitunit_some:
     - git diff --name-only origin/master..HEAD -- "*.nit" "*.res" "README.*" | grep -v "^tests/" > list0.txt || true
     - xargs nitls -pP < list0.txt > list.txt
     - xargs nitunit < list.txt
+    - junit2html nitunit.xml
   artifacts:
     paths:
       - nitunit.xml*
@@ -221,6 +222,7 @@ nitunit_lib:
     - xargs nitunit -v < list.txt| tee log.txt
     - grep -e KO log.txt > status.txt || true
     - tail -3 log.txt >> status.txt
+    - junit2html nitunit.xml
   artifacts:
     paths:
       - nitunit.xml*
@@ -238,6 +240,7 @@ nitunit_src:
     - xargs nitunit -v < list.txt| tee log.txt
     - grep -e KO log.txt > status.txt || true
     - tail -3 log.txt >> status.txt
+    - junit2html nitunit.xml
   artifacts:
     paths:
       - nitunit.xml*
@@ -329,6 +332,21 @@ build_more_tools:
       - src/version.nit
       - src/nitc_0
 
+valgrind:
+  stage: more_test
+  dependencies:
+    - build_more_tools
+  script:
+    - mkdir -p valgrind.out
+    - nitc src/nitc.nit # To warm-up the cache
+    - src/valgrind.sh --callgrind-out-file=valgrind.out/nitc.nitc.out nitc src/nitc.nit -vv
+    - callgrind_annotate valgrind.out/nitc.nitc.out > valgrind.out/nitc.nitc.txt
+    - src/valgrind.sh --callgrind-out-file=valgrind.out/niti.niti.out nit -- src/nit.nit tests/base_simple3.nit -vv
+    - callgrind_annotate valgrind.out/niti.niti.out > valgrind.out/niti.niti.txt
+  artifacts:
+    paths:
+      - valgrind.out
+
 build_doc:
   stage: more_test
   dependencies:
@@ -340,6 +358,16 @@ build_doc:
     paths:
       - nitdoc.out
 
+nitmetrics:
+  stage: more_test
+  dependencies:
+    - build_more_tools
+  script:
+    - nitmetrics --all --log --log-dir nitmetrics.out --dir nitmetrics.out --keep-going lib src
+  artifacts:
+    paths:
+      - nitmetrics.out
+
 build_catalog:
   stage: more_test
   dependencies:
index f386c47..3013321 100644 (file)
@@ -538,6 +538,12 @@ class Bytes
                length += cln
        end
 
+       redef fun has(c)
+       do
+               if not c isa Int then return false
+               return super(c&255)
+       end
+
        #     var b = new Bytes.empty
        #     b.append([104, 101, 108, 108, 111])
        #     assert b.to_s == "hello"
index b51cd22..9e2179b 100644 (file)
@@ -307,6 +307,45 @@ private class StepIterator[E]
        redef fun next_by(step) do real.next_by(step * self.step)
 end
 
+# An iterator that lazyly cache the current item.
+#
+# This class can be used as an helper to build simple iterator with a single and simplier `next_item` method.
+# The only constraint is that `next_item` returns null on the last item, so `null` cannot be a valid element.
+abstract class CachedIterator[E: Object]
+       super Iterator[E]
+
+       # Get the next item if any.
+       # Returns null if there is no next item.
+       fun next_item: nullable E is abstract
+
+       # The last item effectively read.
+       # `null` if on start, after a next of if no more items are available.
+       protected var cache: nullable E = null
+
+       # The current item, if any.
+       # If not, the cache is effectively filled (with `next_item`).
+       # Return `null` iff there is no more elements.
+       protected fun current_item: nullable E
+       do
+               var cache = self.cache
+               if cache != null then return cache
+               cache = next_item
+               self.cache = cache
+               return cache
+       end
+
+       redef fun item do return current_item.as(not null)
+
+       redef fun is_ok do return current_item != null
+
+       redef fun next do
+               # If needed, fill the cache (an consume the current element)
+               current_item
+               # Empty the cache (so the next element will be read)
+               cache = null
+       end
+end
+
 # A collection that contains only one item.
 #
 # Used to pass arguments by reference.
index 152c294..b0486ef 100644 (file)
@@ -484,37 +484,18 @@ end
 # Iterator returned by `Reader::each_line`.
 # See the aforementioned method for details.
 class LineIterator
-       super Iterator[String]
+       super CachedIterator[String]
 
        # The original stream
        var stream: Reader
 
-       redef fun is_ok
+       redef fun next_item
        do
-               var res = not stream.eof
-               if not res and close_on_finish then stream.close
-               return res
-       end
-
-       redef fun item
-       do
-               var line = self.line
-               if line == null then
-                       line = stream.read_line
+               if stream.eof then
+                       if close_on_finish then stream.close
+                       return null
                end
-               self.line = line
-               return line
-       end
-
-       # The last line read (cache)
-       private var line: nullable String = null
-
-       redef fun next
-       do
-               # force the read
-               if line == null then item
-               # drop the line
-               line = null
+               return stream.read_line
        end
 
        # Close the stream when the stream is at the EOF.
index 0704e5f..6a60c5d 100644 (file)
@@ -134,15 +134,19 @@ class LoaderConfig
        # Verbosity level (the higher the more verbose)
        fun verbose_level: Int do
                var opt = opt_start.value
-               if opt > 0 then return opt
+               if opt > 0 then
+                       return info_level
+               end
                var v = ini["loader.verbose"]
-               if v != null then return v.to_i
-               return 4
+               if v != null and v.to_i > 0 then
+                       return info_level
+               end
+               return warn_level
        end
 
        # Logger used to print things
-       var logger: ConsoleLog is lazy do
-               var logger = new ConsoleLog
+       var logger: PopLogger is lazy do
+               var logger = new PopLogger
                logger.level = verbose_level
                return logger
        end
@@ -418,7 +422,7 @@ class Loader
        end
 
        # Logger shortcut
-       fun log: ConsoleLog do return config.logger
+       fun log: PopLogger do return config.logger
 
        # Display a error and exit
        fun error(msg: String) do
diff --git a/lib/logger/logger.nit b/lib/logger/logger.nit
new file mode 100644 (file)
index 0000000..974ff9c
--- /dev/null
@@ -0,0 +1,402 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A simple logger for Nit
+#
+# ## Basic Usage
+#
+# Create a new `Logger` with a severity level threshold set to `warn_level`:
+#
+# ~~~
+# var logger = new Logger(warn_level)
+# ~~~
+#
+# Messages with a severity equal or higher than `warn_level` will be displayed:
+#
+# ~~~
+# logger.error "Displays an error."
+# logger.warn "Displays a warning."
+# ~~~
+#
+# Messages with a lower severity are silenced:
+#
+# ~~~
+# logger.info "Displays nothing."
+# ~~~
+#
+# `FileLogger` can be used to output the messages into a file:
+#
+# ~~~
+# var log_file = "my.log"
+#
+# logger = new FileLogger(warn_level, log_file, append = false)
+# logger.error("An error")
+# logger.info("Some info")
+# logger.close
+#
+# assert log_file.to_path.read_all == "An error\n"
+# log_file.to_path.delete
+# ~~~
+#
+# ## Severity levels
+#
+# Each message is associated with a level that indicate its severity.
+# Only messages with a severity equal to or higher than the logger `level`
+# threshold will be displayed.
+#
+# Severity levels from the most severe to the least severe:
+#
+# * `unknown_level`: An unknown message that should always be outputted.
+# * `fatal_level`: An unhandleable error that results in a program crash.
+# * `error_level`: A handleable error condition.
+# * `warn_level`: A warning.
+# * `info_level`: Generic (useful) information about system operation.
+# * `debug_level`: Low-level information for developpers.
+#
+# ## Formatting messages
+#
+# You can create custom formatters by implementing the `Formatter` interface.
+#
+# ~~~
+# class MyFormatter
+#      super Formatter
+#
+#      redef fun format(level, message) do
+#              if level < warn_level then return super
+#              return "!!!{message}!!!"
+#      end
+# end
+# ~~~
+#
+# See `DefaultFormatter` for a more advanced implementation example.
+#
+# Each Logger can be given a default formatter used to format the every messages
+# before outputting them:
+#
+# ~~~
+# var formatter = new MyFormatter
+# var stderr = new StringWriter
+# var logger = new Logger(warn_level, stderr, formatter)
+#
+# logger.warn("This is a warning.")
+# assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
+# ~~~
+#
+# Optionally, a `Formatter` can be given to replace the `default_formatter`
+# used by default:
+#
+# ~~~
+# # Create a formatter with no default decorator
+# logger = new Logger(warn_level, stderr, null)
+#
+# # Display a message without any formatter
+# logger.warn("This is a warning.")
+# assert stderr.to_s.trim.split("\n").last == "This is a warning."
+#
+# # Display a message with a custom formatter
+# logger.warn("This is a warning.", formatter)
+# assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
+# ~~~
+module logger
+
+import console
+
+# A simple logging utility
+#
+# `Logger` provides a simple way to output messages from applications.
+#
+# Each message is associated with a level that indicate its severity.
+# Only messages with a severity equal to or higher than the logger `level`
+# threshold will be displayed.
+#
+# ~~~
+# var logger = new Logger(warn_level)
+# assert logger.unknown("unkown")
+# assert logger.fatal("fatal")
+# assert logger.error("error")
+# assert logger.warn("warn")
+# assert not logger.info("info")
+# assert not logger.debug("debug")
+# ~~~
+class Logger
+
+       # Severity threshold
+       #
+       # Messages with a severity level greater than or equal to `level` will be displayed.
+       # Default is `warn_level`.
+       #
+       # See `unknown_level`, `fatal_level`, error_level``, `warn_level`,
+       # `info_level` and `debug_level`.
+       var level: Int = warn_level is optional, writable
+
+       # Kind of `Writer` used to output messages
+       type OUT: Writer
+
+       # Writer used to output messages
+       #
+       # Default is `stderr`.
+       var out: OUT = stderr is optional
+
+       # Formatter used to format messages before outputting them
+       #
+       # By default no formatter is used.
+       #
+       # See `DefaultFormatter`.
+       var default_formatter: nullable Formatter = null is optional, writable
+
+       # Output a message with `level` severity
+       #
+       # Only output messages with `level` severity greater than of equal to `self.level`.
+       #
+       # ~~~
+       # var stderr = new StringWriter
+       # var logger = new Logger(warn_level, stderr, null)
+       #
+       # # This message will be displayed:
+       # assert logger.warn("This is a warning.")
+       # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+       #
+       # # This message will not:
+       # assert not logger.info("This is some info.")
+       # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+       # ~~~
+       #
+       # Each logger can be given a default formatter used to format the messages
+       # before outputting them:
+       #
+       # ~~~
+       # var formatter = new DefaultFormatter(no_color = true)
+       # logger = new Logger(warn_level, stderr, formatter)
+       # logger.warn("This is a warning.")
+       # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
+       # ~~~
+       #
+       # Optionally, a `Formatter` can be given to replace the `default_formatter`
+       # used by default.
+       #
+       # ~~~
+       # # Create a formatter with no default decorator
+       # logger = new Logger(warn_level, stderr, null)
+       #
+       # # Display a message without any formatter
+       # logger.warn("This is a warning.")
+       # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+       #
+       # # Display a message with a custom formatter
+       # logger.warn("This is a warning.", formatter)
+       # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
+       # ~~~
+       fun add(level: Int, message: Writable, formatter: nullable Formatter): Bool do
+               var format = formatter or else default_formatter
+               if format == null then
+                       return add_raw(level, message)
+               end
+               return add_raw(level, format.format(level, message))
+       end
+
+       # Output a message with `level` severity without formatting it
+       #
+       # Only output messages with `level` severity greater than of equal to `self.level`.
+       #
+       # ~~~
+       # var stderr = new StringWriter
+       # var logger = new Logger(warn_level, stderr, null)
+       #
+       # # This message will be displayed:
+       # assert logger.add_raw(warn_level, "This is a warning.")
+       # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+       #
+       # # This message will not:
+       # assert not logger.add_raw(info_level, "This is some info.")
+       # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+       # ~~~
+       fun add_raw(level: Int, message: Writable): Bool do
+               if level < self.level then return false
+               out.write(message.write_to_string)
+               out.write("\n")
+               return true
+       end
+
+       # Output a message with `unknown_level` severity
+       #
+       # Unkown severity messages are always outputted.
+       fun unknown(message: String, formatter: nullable Formatter): Bool do
+               return add(unknown_level, message, formatter)
+       end
+
+       # Output a message with `fatal_level` severity
+       fun fatal(message: String, formatter: nullable Formatter): Bool do
+               return add(fatal_level, message, formatter)
+       end
+
+       # Output a message with `error_level` severity
+       fun error(message: String, formatter: nullable Formatter): Bool do
+               return add(error_level, message, formatter)
+       end
+
+       # Output a message with `warn_level` severity
+       fun warn(message: String, formatter: nullable Formatter): Bool do
+               return add(warn_level, message, formatter)
+       end
+
+       # Output a message with `info_level` severity
+       fun info(message: String, formatter: nullable Formatter): Bool do
+               return add(info_level, message, formatter)
+       end
+
+       # Output a message with `debug` severity
+       fun debug(message: String, formatter: nullable Formatter): Bool do
+               return add(debug_level, message, formatter)
+       end
+end
+
+# Log messages to a file
+#
+# ~~~
+# var log_file = "my_file.log"
+# var logger = new FileLogger(warn_level, log_file, append = false)
+# logger.error("An error")
+# logger.info("Some info")
+# logger.close
+# assert log_file.to_path.read_all == "An error\n"
+#
+# logger = new FileLogger(warn_level, log_file, append = true)
+# logger.error("Another error")
+# logger.close
+# assert log_file.to_path.read_all == "An error\nAnother error\n"
+#
+# log_file.to_path.delete
+# ~~~
+class FileLogger
+       super Logger
+       autoinit level, file, append, default_formatter
+
+       redef type OUT: FileWriter
+
+       # File where messages will be written
+       var file: String
+
+       # Append messages to `file`
+       #
+       # If `append` is `false`, the `file` will be overwritten.
+       var append: Bool = true is optional
+
+       init do
+               var old = null
+               if append then
+                       old = file.to_path.read_all
+               end
+               out = new FileWriter.open(file)
+               out.set_buffering_mode(0, buffer_mode_line)
+               if old != null then
+                       out.write(old)
+               end
+       end
+
+       # Close the logger and its `file`
+       fun close do out.close
+end
+
+# Format messages before outputing them
+#
+# A `Logger` can use a `Formatter` to format the messages before outputting them.
+#
+# See `DefaultFormatter`.
+interface Formatter
+
+       # Format `message` depending of its severity `level`
+       fun format(level: Int, message: Writable): Writable do return message
+end
+
+# Default `Logger` formatter
+#
+# The default formatter decorates the messages with severity labels and colors.
+class DefaultFormatter
+       super Formatter
+
+       # Do not decorate messages with colors
+       #
+       # ~~~
+       # var formatter = new DefaultFormatter(no_color = true)
+       # assert formatter.format(error_level, "My message.") == "Error: My message."
+       # ~~~
+       var no_color = false is optional, writable
+
+       redef fun format(level, message) do
+               var string = message.write_to_string
+
+               if level == fatal_level then
+                       string = "Fatal: {string}"
+               else if level == error_level then
+                       string = "Error: {string}"
+               else if level == warn_level then
+                       string = "Warning: {string}"
+               else if level == info_level then
+                       string = "Info: {string}"
+               else if level == debug_level then
+                       string = "Debug: {string}"
+               end
+
+               if no_color then return string
+
+               if level == fatal_level then
+                       return string.red
+               else if level == error_level then
+                       return string.red
+               else if level == warn_level then
+                       return string.yellow
+               else if level == info_level then
+                       return string.purple
+               else if level == debug_level then
+                       return string.blue
+               end
+
+               return string
+       end
+end
+
+redef class Sys
+
+       # Unknown severity level
+       #
+       # These messages are always displayed.
+       #
+       # See `Logger`.
+       var unknown_level = 5
+
+       # Fatal severity level
+       #
+       # See `Logger`.
+       var fatal_level = 4
+
+       # Error severity level
+       #
+       # See `Logger`.
+       var error_level = 3
+
+       # Warning severity level
+       #
+       # See `Logger`.
+       var warn_level = 2
+
+       # Info severity level
+       #
+       # See `Logger`.
+       var info_level = 1
+
+       # Debug severity level
+       #
+       # See `Logger`.
+       var debug_level = 0
+end
diff --git a/lib/logger/package.ini b/lib/logger/package.ini
new file mode 100644 (file)
index 0000000..f4ba7bd
--- /dev/null
@@ -0,0 +1,12 @@
+[package]
+name=logger
+tags=logging,lib
+maintainer=Alexandre Terrasa <alexandre@moz-code.org>
+license=Apache-2.0
+desc=A simple logger for Nit
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/logger/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/logger/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
index f7c6346..50a0de6 100644 (file)
@@ -511,7 +511,7 @@ with the `use_before` method.
 Next, we’ll create a middleware handler called “LogHandler” that prints the requested
 uri, the response status and the time it took to Popcorn to process the request.
 
-This example gives a simplified version of the `RequestClock` and `ConsoleLog` middlewares.
+This example gives a simplified version of the `RequestClock` and `PopLogger` middlewares.
 
 ~~~
 import popcorn
@@ -584,7 +584,7 @@ Starting with version 0.1, Popcorn provide a set of built-in middleware that can
 be used to develop your app faster.
 
 * `RequestClock`: initializes requests clock.
-* `ConsoleLog`: displays resquest and response status in console (can be used with `RequestClock`).
+* `PopLogger`: displays resquest and response status in console (can be used with `RequestClock`).
 * `SessionInit`: initializes requests session (see the `Sessions` section).
 * `StaticServer`: serves static files (see the `Serving static files with Popcorn` section).
 * `Router`: a mountable mini-app (see the `Mountable routers` section).
index ba3418f..6f0d154 100644 (file)
@@ -17,7 +17,7 @@
 module pop_logging
 
 import pop_handlers
-import console
+import logger
 import realtime
 
 # Initialize a clock for the resquest.
@@ -30,73 +30,71 @@ class RequestClock
 end
 
 # Display log info about request processing.
-class ConsoleLog
+class PopLogger
+       super Logger
        super Handler
 
-       # Logger level
-       #
-       # * `0`: silent
-       # * `1`: errors
-       # * `2`: warnings
-       # * `3`: info
-       # * `4`: debug
-       #
-       # Request status are always logged, whatever the logger level is.
-       var level = 4 is writable
-
        # Do we want colors in the console output?
-       var no_colors = false
+       var no_color = false is optional
+
+       redef var default_formatter = new PopFormatter(no_color) is optional
 
        redef fun all(req, res) do
                var clock = req.clock
                if clock != null then
-                       log "{req.method} {req.url} {status(res)} ({clock.total}s)"
+                       add_raw(info_level, "{req.method} {req.url} {status(res)} ({clock.total}s)")
                else
-                       log "{req.method} {req.url} {status(res)}"
+                       add_raw(info_level, "{req.method} {req.url} {status(res)}")
                end
        end
 
        # Colorize the request status.
        private fun status(res: HttpResponse): String do
-               if no_colors then return res.status_code.to_s
+               if no_color then return res.status_code.to_s
                return res.color_status
        end
+end
 
-       # Display a `message` with `level`.
-       #
-       # Message will only be displayed if `level <= self.level`.
-       # Colors will be used depending on `colors`.
-       #
-       # Use `0` for no coloration.
-       private fun display(level: Int, message: String) do
-               if level > self.level then return
-               if no_colors then
-                       print message
-                       return
+class PopFormatter
+       super Formatter
+
+       # Do not decorate messages with colors
+       var no_color = false is optional, writable
+
+       redef fun format(level, message) do
+               var string = message.write_to_string
+
+               if level == fatal_level then
+                       string = "[FATAL] {string}"
+               else if level == error_level then
+                       string = "[ERROR] {string}"
+               else if level == warn_level then
+                       string = "[WARN] {string}"
+               else if level == info_level then
+                       string = "[INFO] {string}"
+               else if level == debug_level then
+                       string = "[DEBUG] {string}"
                end
-               if level == 0 then print message
-               if level == 1 then print message.red
-               if level == 2 then print message.yellow
-               if level == 3 then print message.blue
-               if level == 4 then print message.gray
-       end
-
-       # Display a message wathever the `level`
-       fun log(message: String) do display(0, message)
 
-       # Display a red error `message`.
-       fun error(message: String) do display(1, "[ERROR] {message}")
-
-       # Display a yellow warning `message`.
-       fun warning(message: String) do display(2, "[WARN] {message}")
-
-       # Display a blue info `message`.
-       fun info(message: String) do display(3, "[INFO] {message}")
+               if no_color then return string
+
+               if level == fatal_level then
+                       return string.red
+               else if level == error_level then
+                       return string.red
+               else if level == warn_level then
+                       return string.yellow
+               else if level == info_level then
+                       return string.blue
+               else if level == debug_level then
+                       return string.gray
+               end
 
-       # Display a gray debug `message`.
-       fun debug(message: String) do display(4, "[DEBUG] {message}")
+               return string
+       end
 end
 
+
 redef class HttpRequest
        # Time that request was received by the Popcorn app.
        var clock: nullable Clock = null
index 9ecf5b7..f43b100 100644 (file)
@@ -46,7 +46,6 @@ module pop_tracker
 
 import popcorn
 import popcorn::pop_config
-import popcorn::pop_logging
 import popcorn::pop_json
 import popcorn::pop_repos
 
@@ -91,7 +90,6 @@ end
 
 # Saves logs into a MongoDB collection
 class PopTracker
-       super ConsoleLog
        super TrackerHandler
 
        redef fun all(req, res) do
index 3c5ae98..a8b4bbc 100644 (file)
@@ -421,6 +421,14 @@ class ConcurrentArray[E]
                mutex.unlock
        end
 
+       redef fun has(e)
+       do
+               mutex.lock
+               var result = real_collection.has(e)
+               mutex.unlock
+               return result
+       end
+
        #
        ## The following method defs are conflict resolutions
        #
index e4ba62b..69dbc45 100644 (file)
@@ -13,6 +13,7 @@ RUN dpkg --add-architecture i386 \
                graphviz \
                libunwind-dev \
                pkg-config \
+               libicu-dev \
                # Get the code!
                git \
                ca-certificates \
index fe8fb7b..125c514 100644 (file)
@@ -84,19 +84,23 @@ for a in args do
        end
        # Try to load the file as a markdown document
        var mdoc = modelbuilder.load_markdown(a)
-       page.add modelbuilder.test_mdoc(mdoc)
+       var ts = modelbuilder.test_mdoc(mdoc)
+       if not ts.children.is_empty then page.add ts
 end
 
 for a in module_files do
        var g = modelbuilder.identify_group(a)
        if g == null then continue
-       page.add modelbuilder.test_group(g)
+       var ts = modelbuilder.test_group(g)
+       if not ts.children.is_empty then page.add ts
 end
 
 for m in mmodules do
-       page.add modelbuilder.test_markdown(m)
-       var ts = modelbuilder.test_unit(m)
-       if ts != null then page.add ts
+       var ts
+       ts = modelbuilder.test_markdown(m)
+       if not ts.children.is_empty then page.add ts
+       ts = modelbuilder.test_unit(m)
+       if ts != null and not ts.children.is_empty then page.add ts
 end
 
 var file = toolcontext.opt_output.value
index 9e9d54f..d3c8c63 100644 (file)
@@ -99,7 +99,7 @@ private class NitwebPhase
                app.use("/oauth", new GithubOAuthCallBack(config.github_client_id, config.github_client_secret))
                app.use("/logout", new GithubLogout)
                app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
-               app.use_after("/*", new ConsoleLog)
+               app.use_after("/*", new PopLogger(info_level))
 
                app.listen(config.app_host, config.app_port)
        end
index f1851ee..7cec735 100644 (file)
@@ -35,5 +35,5 @@ Test suites: Classes: 1; Test Cases: 3; Failures: 1
 </system-out></testcase><testcase classname="nitunit.test_nitunit.X" name="foo1" time="0.0"><failure message="Syntax Error: unexpected operator &#39;!&#39;."></failure><system-out>assert !@#$%^&amp;*()
 </system-out></testcase><testcase classname="nitunit.test_nitunit.X" name="foo2" time="0.0"><system-err></system-err><system-out>var x = new X
 assert x.foo2
-</system-out></testcase></testsuite><testsuite package="test_test_nitunit::test_test_nitunit"></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo1" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_test_nitunit.nit">Runtime error: Assert failed (test_test_nitunit.nit:38)
+</system-out></testcase></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo1" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_test_nitunit.nit">Runtime error: Assert failed (test_test_nitunit.nit:38)
 </error></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo2" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index 5fdb314..cbfc0cd 100644 (file)
@@ -5,4 +5,4 @@
 Docunits: Entities: 4; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 2; Failures: 0
 [SUCCESS] All 2 tests passed.
-<testsuites><testsuite package="test_nitunit5::test_nitunit5"></testsuite><testsuite package="test_nitunit5"><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_set" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_suite_path" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit5"><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_set" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_suite_path" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index 0d1829d..a640323 100644 (file)
@@ -13,4 +13,4 @@ Docunits: Entities: 5; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 3; Failures: 3
 [FAILURE] 3/3 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit6::test_nitunit6"></testsuite><testsuite package="test_nitunit6"><testcase classname="nitunit.test_nitunit6.TestNitunit6" name="test_foo" time="0.0"><failure message="Nitunit Error: before module test failed"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit6"><testcase classname="nitunit.test_nitunit6.TestNitunit6" name="test_foo" time="0.0"><failure message="Nitunit Error: before module test failed"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
index 9015b4a..4932785 100644 (file)
@@ -11,4 +11,4 @@ Docunits: Entities: 5; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 3; Failures: 1
 [FAILURE] 1/3 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit7::test_nitunit7"></testsuite><testsuite package="test_nitunit7"><testcase classname="nitunit.test_nitunit7.TestNitunit7" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit7"><testcase classname="nitunit.test_nitunit7.TestNitunit7" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index 8c8d3f9..84dbc07 100644 (file)
@@ -11,4 +11,4 @@ Docunits: Entities: 3; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 3; Failures: 1
 [FAILURE] 1/3 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit8::test_nitunit8"></testsuite><testsuite package="test_nitunit8"><testcase classname="nitunit.test_nitunit8.TestNitunit8" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit8"><testcase classname="nitunit.test_nitunit8.TestNitunit8" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index c0f1e23..3cb2d30 100644 (file)
@@ -15,4 +15,4 @@ Docunits: Entities: 7; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 7; Failures: 1
 [FAILURE] 1/7 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit11::test_nitunit11"></testsuite><testsuite package="test_nitunit11"><testcase classname="nitunit.test_nitunit11.TestNitunit11" name="test_baz" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit11"><testcase classname="nitunit.test_nitunit11.TestNitunit11" name="test_baz" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
index 404c270..7682e35 100644 (file)
@@ -59,11 +59,11 @@ Docunits: Entities: 22; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 3; Test Cases: 8; Failures: 7
 [FAILURE] 7/8 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit4&gt;"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4::test_bad_comp"></testsuite><testsuite package="test_bad_comp"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+<testsuites><testsuite package="test_bad_comp"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
 </failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">test_nitunit4&#47;test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
-</failure></testcase></testsuite><testsuite package="test_nitunit4::test_bad_comp2"></testsuite><testsuite package="test_bad_comp2"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
+</failure></testcase></testsuite><testsuite package="test_bad_comp2"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
 </failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">nitunit.out&#47;gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
-</failure></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_foo" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_nitunit4.nit">Before Test
+</failure></testcase></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_foo" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_nitunit4.nit">Before Test
 Tested method
 After Test
 Runtime assert: &lt;TestTestSuite&gt;.before
@@ -82,4 +82,4 @@ After Test
 </error></testcase><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_sav_conflict" time="0.0"><error message="Conflicting expected output: test_nitunit4&#47;test_nitunit4.sav&#47;test_sav_conflict.res, test_nitunit4&#47;sav&#47;test_sav_conflict.res and test_nitunit4&#47;test_sav_conflict.res all exist">Before Test
 Tested method
 After Test
-</error></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite></testsuites>
\ No newline at end of file
+</error></testcase></testsuite></testsuites>
\ No newline at end of file
diff --git a/tests/sav/test_file_read3.res b/tests/sav/test_file_read3.res
new file mode 100644 (file)
index 0000000..461ab2d
--- /dev/null
@@ -0,0 +1,263 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+true
+#
+true
+# Copyright 2004-2008 Jean Privat <jean@pryen.org>
+true
+#
+true
+# Licensed under the Apache License, Version 2.0 (the "License");
+true
+# you may not use this file except in compliance with the License.
+true
+# You may obtain a copy of the License at
+true
+#
+true
+#     http://www.apache.org/licenses/LICENSE-2.0
+true
+#
+true
+# Unless required by applicable law or agreed to in writing, software
+true
+# distributed under the License is distributed on an "AS IS" BASIS,
+true
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+true
+# See the License for the specific language governing permissions and
+true
+# limitations under the License.
+true
+
+true
+var f = new FileReader.open("test_file_read.nit")
+true
+var s: String
+true
+while not f.eof do
+true
+    s = f.read_line
+true
+    printn(s)
+true
+    printn("\n")
+true
+end
+true
+f.close
+true
+
+true
+f.reopen
+true
+printn(f.read(10))
+true
+printn("|")
+true
+printn(f.read_all)
+true
+---
+true
+# This file is part of NIT ( http://www.nitlanguage.org ).
+true
+# This file is part of NIT ( http://www.nitlanguage.org ).
+true
+true
+#
+true
+#
+true
+true
+# Copyright 2004-2008 Jean Privat <jean@pryen.org>
+true
+# Copyright 2004-2008 Jean Privat <jean@pryen.org>
+true
+true
+#
+true
+#
+true
+true
+# Licensed under the Apache License, Version 2.0 (the "License");
+true
+# Licensed under the Apache License, Version 2.0 (the "License");
+true
+true
+# you may not use this file except in compliance with the License.
+true
+# you may not use this file except in compliance with the License.
+true
+true
+# You may obtain a copy of the License at
+true
+# You may obtain a copy of the License at
+true
+true
+#
+true
+#
+true
+true
+#     http://www.apache.org/licenses/LICENSE-2.0
+true
+#     http://www.apache.org/licenses/LICENSE-2.0
+true
+true
+#
+true
+#
+true
+true
+# Unless required by applicable law or agreed to in writing, software
+true
+# Unless required by applicable law or agreed to in writing, software
+true
+true
+# distributed under the License is distributed on an "AS IS" BASIS,
+true
+# distributed under the License is distributed on an "AS IS" BASIS,
+true
+true
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+true
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+true
+true
+# See the License for the specific language governing permissions and
+true
+# See the License for the specific language governing permissions and
+true
+true
+# limitations under the License.
+true
+# limitations under the License.
+true
+true
+
+true
+
+true
+true
+var f = new FileReader.open("test_file_read.nit")
+true
+var f = new FileReader.open("test_file_read.nit")
+true
+true
+var s: String
+true
+var s: String
+true
+true
+while not f.eof do
+true
+while not f.eof do
+true
+true
+    s = f.read_line
+true
+    s = f.read_line
+true
+true
+    printn(s)
+true
+    printn(s)
+true
+true
+    printn("\n")
+true
+    printn("\n")
+true
+true
+end
+true
+end
+true
+true
+f.close
+true
+f.close
+true
+true
+
+true
+
+true
+true
+f.reopen
+true
+f.reopen
+true
+true
+printn(f.read(10))
+true
+printn(f.read(10))
+true
+true
+printn("|")
+true
+printn("|")
+true
+true
+printn(f.read_all)
+true
+printn(f.read_all)
+true
+---
+# This file is part of NIT ( http://www.nitlanguage.org ).
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+#
+# Copyright 2004-2008 Jean Privat <jean@pryen.org>
+# Copyright 2004-2008 Jean Privat <jean@pryen.org>
+#
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# You may obtain a copy of the License at
+#
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#
+# Unless required by applicable law or agreed to in writing, software
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# limitations under the License.
+
+
+var f = new FileReader.open("test_file_read.nit")
+var f = new FileReader.open("test_file_read.nit")
+var s: String
+var s: String
+while not f.eof do
+while not f.eof do
+    s = f.read_line
+    s = f.read_line
+    printn(s)
+    printn(s)
+    printn("\n")
+    printn("\n")
+end
+end
+f.close
+f.close
+
+
+f.reopen
+f.reopen
+printn(f.read(10))
+printn(f.read(10))
+printn("|")
+printn("|")
+printn(f.read_all)
+printn(f.read_all)
diff --git a/tests/test_file_read3.nit b/tests/test_file_read3.nit
new file mode 100644 (file)
index 0000000..f81a854
--- /dev/null
@@ -0,0 +1,52 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+var f
+var i
+var l = 0
+
+f = new FileReader.open("test_file_read.nit")
+i = f.each_line
+while i.is_ok do
+       l += 1
+       print i.item
+       print i.is_ok
+       i.next
+end
+f.close
+
+print "---"
+
+f = new FileReader.open("test_file_read.nit")
+i = f.each_line
+while i.is_ok do
+       print i.is_ok
+       print i.item
+       print i.is_ok
+       print i.item
+       print i.is_ok
+       i.next
+end
+f.close
+
+print "---"
+
+f = new FileReader.open("test_file_read.nit")
+i = f.each_line
+for x in [0..l[ do
+       print i.item
+       print i.item
+       i.next
+end
+f.close
index 721171d..c19a9a8 100755 (executable)
@@ -846,6 +846,10 @@ fi
 
 echo >>$xml "</testsuite></testsuites>"
 
+if type junit2html >/dev/null; then
+       junit2html "$xml"
+fi
+
 if [ -n "$nok" ]; then
        exit 1
 else