Skip to content

Commit

Permalink
🚑 Add logger impl to stdlib (opal#474)
Browse files Browse the repository at this point in the history
Temporary fix until opal#1802 is merged

* Add logger impl to stdlib
* Add a wrapper API around the Cursor object (resolves opal#475)
  • Loading branch information
ggrossetie committed Apr 16, 2018
1 parent ae84c84 commit c3ab34e
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 9 deletions.
47 changes: 39 additions & 8 deletions spec/node/asciidoctor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function fileExists (path) {
try {
fs.statSync(path);
return true;
} catch(err) {
} catch (err) {
return !(err && err.code === 'ENOENT');
}
}
Expand All @@ -48,6 +48,37 @@ describe('Node.js', () => {
});
});

if (asciidoctor.LoggerManager) {
describe('Logger', () => {
it('should warn if part has no sections', () => {
const input = `= Book
:doctype: book
= Part 1
[partintro]
intro
`;
const defaultLogger = asciidoctor.LoggerManager.getLogger();
const memoryLogger = asciidoctor.MemoryLogger.$new();
try {
asciidoctor.LoggerManager.setLogger(memoryLogger);
asciidoctor.convert(input);
const errorMessage = memoryLogger.getMessages()[0];
expect(errorMessage.severity.toString()).toBe('ERROR');
expect(errorMessage.message['text']).toBe('invalid part, must have at least one section (e.g., chapter, appendix, etc.)');
const sourceLocation = errorMessage.message['source_location'];
expect(sourceLocation.getLineNumber()).toBe(8);
expect(sourceLocation.getFile()).toBeUndefined();
expect(sourceLocation.getDirectory()).toBe(process.cwd());
expect(sourceLocation.getPath()).toBe('<stdin>');
} finally {
asciidoctor.LoggerManager.setLogger(defaultLogger);
}
});
});
}

describe('Configuring Asciidoctor module', () => {
it('should be able to configure Asciidoctor module', () => {
expect(Opal.JAVASCRIPT_IO_MODULE).toBe('node');
Expand Down Expand Up @@ -163,22 +194,22 @@ describe('Node.js', () => {

it('should be able to find blocks', () => {
const doc = asciidoctor.loadFile(__dirname + '/documentblocks.adoc');
const quoteBlocks = doc.findBy(function (b) { return b.getStyle() === 'quote'; });
const quoteBlocks = doc.findBy((b) => b.getStyle() === 'quote');
expect(quoteBlocks.length).toBe(1);

const sectionBlocks = doc.findBy({'context': 'section'});
expect(sectionBlocks.length).toBe(5);

const abstractSectionBlocks = doc.findBy({'context': 'section'}, function (b) { return b.getTitle() === 'Second Section'; });
const abstractSectionBlocks = doc.findBy({'context': 'section'}, (b) => b.getTitle() === 'Second Section');
expect(abstractSectionBlocks.length).toBe(1);
});

it('should be able to find blocks with line number', () => {
const doc = asciidoctor.loadFile(__dirname + '/documentblocks.adoc', {sourcemap: true});
const blocks = doc.findBy(function () { return true; });
const blocks = doc.findBy(() => true);
expect(blocks.length).toBe(26);

const blocksWithLineNumber = doc.findBy(function (b) { return typeof b.getLineNumber() !== 'undefined'; });
const blocksWithLineNumber = doc.findBy((b) => typeof b.getLineNumber() !== 'undefined');
// since https://github.com/asciidoctor/asciidoctor/commit/46700a9c12d1cfe551db2790dd232baa0bec8195
// When the sourcemap option is specified, the source location (and as a consequence the line number) is defined on the Document object.
expect(blocksWithLineNumber.length >= 18).toBe(true);
Expand Down Expand Up @@ -357,7 +388,7 @@ Content 2`;
asciidoctor.Extensions.register(function () {
this.includeProcessor(LoremIncludeProcessor);
});
const html = asciidoctor.convert('include::fake.adoc[]', { safe: 'safe' });
const html = asciidoctor.convert('include::fake.adoc[]', {safe: 'safe'});
expect(html).toContain('Lorem ipsum');
} finally {
asciidoctor.Extensions.unregisterAll();
Expand Down Expand Up @@ -561,7 +592,7 @@ Content 2`;
self.named('whisper');
self.onContext('paragraph');
self.process(function (parent, reader) {
const lines = reader.getLines().map(function (l) { return l.toLowerCase().replace('!', '.'); });
const lines = reader.getLines().map((l) => l.toLowerCase().replace('!', '.'));
return self.createBlock(parent, 'paragraph', lines);
});
});
Expand All @@ -576,7 +607,7 @@ Content 2`;
this.blockMacro(function () {
this.named('img');
this.process((parent, target) => {
return this.createImageBlock(parent, { target: target + '.png' });
return this.createImageBlock(parent, {target: target + '.png'});
});
});
});
Expand Down
87 changes: 87 additions & 0 deletions src/asciidoctor-core-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1188,3 +1188,90 @@ Reader.$$proto.getLines = function () {
Reader.$$proto.getString = function () {
return this.$string();
};

// Cursor API

/** @namespace */
var Cursor = Opal.Asciidoctor.Reader.Cursor;

/**
* Get the file associated to the cursor.
* @memberof Cursor
*/
Cursor.$$proto.getFile = function () {
var file = this.file;
return file === Opal.nil ? undefined : file;
};

/**
* Get the directory associated to the cursor.
* @memberof Cursor
* @returns {string} - returns the directory associated to the cursor
*/
Cursor.$$proto.getDirectory = function () {
var dir = this.dir;
return dir === Opal.nil ? undefined : dir;
};

/**
* Get the path associated to the cursor.
* @memberof Cursor
* @returns {string} - returns the path associated to the cursor (or '<stdin>')
*/
Cursor.$$proto.getPath = function () {
var path = this.path;
return path === Opal.nil ? undefined : path;
};

/**
* Get the line number of the cursor.
* @memberof Cursor
* @returns {number} - returns the line number of the cursor
*/
Cursor.$$proto.getLineNumber = function () {
return this.lineno;
};

// Logger API (available in Asciidoctor 1.5.7+)
// REMIND: we are using "skip_missing" because this API is only available starting with Asciidoctor 1.5.7

/**
* @namespace
*/
var LoggerManager = Opal.const_get_qualified(Opal.Asciidoctor, 'LoggerManager', true);

// Alias
Opal.Asciidoctor.LoggerManager = LoggerManager;

if (LoggerManager) {
LoggerManager.getLogger = function () {
return this.$logger();
};

LoggerManager.setLogger = function (logger) {
this.logger = logger;
};
}

/**
* @namespace
*/
var MemoryLogger = Opal.const_get_qualified(Opal.Asciidoctor, 'MemoryLogger', true);

// Alias
Opal.Asciidoctor.MemoryLogger = MemoryLogger;

if (MemoryLogger) {
MemoryLogger.$$proto.getMessages = function () {
var messages = this.messages;
var result = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
var messageObject = fromHash(message);
// also convert the message attribute
messageObject.message = fromHash(messageObject.message);
result.push(messageObject);
}
return result;
};
}
2 changes: 1 addition & 1 deletion src/asciidoctor-extensions-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var registerExtension = function (registry, type, processor, name) {
* self.named('shout');
* self.onContext('paragraph');
* self.process(function (parent, reader) {
* var lines = reader.$lines().map(function (l) { return l.toUpperCase(); });
* var lines = reader.getLines().map(function (l) { return l.toUpperCase(); });
* return self.createBlock(parent, 'paragraph', lines);
* });
* });
Expand Down
114 changes: 114 additions & 0 deletions stdlib/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# TODO: Remove when this class will be available in Opal
# https://github.com/opal/opal/pull/1802
class Logger
module Severity
DEBUG = 0
INFO = 1
WARN = 2
ERROR = 3
FATAL = 4
UNKNOWN = 5
end
include Severity

SEVERITY_LABELS = Severity.constants.map { |s| [(Severity.const_get s), s.to_s] }.to_h

class Formatter
MESSAGE_FORMAT = "%s, [%s] %5s -- %s: %s\n"
DATE_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%6N'

def call(severity, time, progname, msg)
format(MESSAGE_FORMAT, severity.chr, time.strftime(DATE_TIME_FORMAT), severity, progname, message_as_string(msg))
end

def message_as_string(msg)
case msg
when ::String
msg
when ::Exception
"#{msg.message} (#{msg.class})\n" + (msg.backtrace || []).join("\n")
else
msg.inspect
end
end
end

attr_reader :level
attr_accessor :progname
attr_accessor :formatter

def initialize(pipe)
@pipe = pipe
@level = DEBUG
@formatter = Formatter.new
end

def level=(severity)
if ::Integer === severity
@level = severity
elsif (level = SEVERITY_LABELS.key(severity.to_s.upcase))
@level = level
else
raise ArgumentError, "invalid log level: #{severity}"
end
end

def info(progname = nil, &block)
add INFO, nil, progname, &block
end

def debug(progname = nil, &block)
add DEBUG, nil, progname, &block
end

def warn(progname = nil, &block)
add WARN, nil, progname, &block
end

def error(progname = nil, &block)
add ERROR, nil, progname, &block
end

def fatal(progname = nil, &block)
add FATAL, nil, progname, &block
end

def unknown(progname = nil, &block)
add UNKNOWN, nil, progname, &block
end

def info?
@level <= INFO
end

def debug?
@level <= DEBUG
end

def warn?
@level <= WARN
end

def error?
@level <= ERROR
end

def fatal?
@level <= FATAL
end

def add(severity, message = nil, progname = nil, &block)
return true if (severity ||= UNKNOWN) < @level
progname ||= @progname
unless message
if block_given?
message = yield
else
message = progname
progname = @progname
end
end
@pipe.write(@formatter.call(SEVERITY_LABELS[severity] || 'ANY', ::Time.now, progname, message))
true
end
end

0 comments on commit c3ab34e

Please sign in to comment.