Skip to content

Commit

Permalink
apacheGH-37835: [MATLAB] Improve arrow.tabular.Schema display (apac…
Browse files Browse the repository at this point in the history
…he#37836)

### Rationale for this change

We would like to change how `arrow.tabular.Schema`s are displayed in the Command Window. Below is the current display:

```matlab
>> field1 =  arrow.field("A", arrow.time32());
>> field2 = arrow.field("B", arrow.boolean());
>>s = arrow.schema([field1 field2])

s = 

A: time32[s]
B: bool
```

This display is not very MATLAB-like.

### What changes are included in this PR?

1. Updated the display of `arrow.tabular.Schema`. Below is the new display:

```matlab
>> field1 =  arrow.field("A", arrow.time32());
>> field2 = arrow.field("B", arrow.boolean());
>> s = arrow.schema([field1 field2])

s = 

  Arrow Schema with 2 fields:

    A: Time32 | B: Boolean
```

When MATLAB is opened in desktop mode, `Schema`, `Time32`, and `Boolean` are hyperlinks users can click on to view the help text for the different class types. 

### Are these changes tested?

Yes. Added three new test cases to `tSchema.m`: 
1. `TestDisplaySchemaZeroFields`
2. `TestDisplaySchemaOneField`
3. `TestDisplaySchemaMultipleFields`

### Are there any user-facing changes?

Yes. `arrow.tabular.Schema`s will be displayed differently in the Command Window.

### Notes

Once apache#37826 is merged, I will rebase my changes and mark this PR as ready for review.
* Closes: apache#37835

Authored-by: Sarah Gilmore <sgilmore@mathworks.com>
Signed-off-by: Kevin Gurney <kgurney@mathworks.com>
  • Loading branch information
sgilmore10 authored and Jeremy Aguilon committed Oct 23, 2023
1 parent e3f25cb commit 814151d
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 19 deletions.
11 changes: 0 additions & 11 deletions matlab/src/cpp/arrow/matlab/tabular/proxy/schema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ namespace arrow::matlab::tabular::proxy {
REGISTER_METHOD(Schema, getFieldByName);
REGISTER_METHOD(Schema, getNumFields);
REGISTER_METHOD(Schema, getFieldNames);
REGISTER_METHOD(Schema, toString);
}

libmexclass::proxy::MakeResult Schema::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) {
Expand Down Expand Up @@ -141,14 +140,4 @@ namespace arrow::matlab::tabular::proxy {
context.outputs[0] = field_names_mda;
}

void Schema::toString(libmexclass::proxy::method::Context& context) {
namespace mda = ::matlab::data;
mda::ArrayFactory factory;

const auto str_utf8 = schema->ToString();
MATLAB_ASSIGN_OR_ERROR_WITH_CONTEXT(const auto str_utf16, arrow::util::UTF8StringToUTF16(str_utf8), context, error::UNICODE_CONVERSION_ERROR_ID);
auto str_mda = factory.createScalar(str_utf16);
context.outputs[0] = str_mda;
}

}
1 change: 0 additions & 1 deletion matlab/src/cpp/arrow/matlab/tabular/proxy/schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ namespace arrow::matlab::tabular::proxy {
void getFieldByName(libmexclass::proxy::method::Context& context);
void getNumFields(libmexclass::proxy::method::Context& context);
void getFieldNames(libmexclass::proxy::method::Context& context);
void toString(libmexclass::proxy::method::Context& context);

std::shared_ptr<arrow::Schema> schema;
};
Expand Down
50 changes: 50 additions & 0 deletions matlab/src/matlab/+arrow/+tabular/+internal/displaySchema.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
%DISPLAYSCHEMA Generates arrow.tabular.Schema display text.

% Licensed to the Apache Software Foundation (ASF) under one or more
% contributor license agreements. See the NOTICE file distributed with
% this work for additional information regarding copyright ownership.
% The ASF licenses this file to you 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.

function text = displaySchema(schema)
fields = schema.Fields;
names = [fields.Name];
types = [fields.Type];
typeIDs = string([types.ID]);

% Use <empty> as the sentinel for field names with zero characters.
idx = strlength(names) == 0;
names(idx) = "<empty>";

if usejava("desktop")
% When in desktop mode, the Command Window can interpret HTML tags
% to display bold font and hyperlinks.
names = compose("<strong>%s</strong>", names);
classNames = arrayfun(@(type) string(class(type)), types);

% Creates a string array with the following form:
%
% ["arrow.type.BooleanType" "Boolean" "arrow.type.StringType" "String" ...]
%
% This string array is passed to the compose call below. The
% format specifier operator supplied to compose contains two
% formatting operators (%s), so compose uses two elements from the
% string array (classNameAndIDs) at a time.
classNameAndIDs = strings([1 numel(typeIDs) * 2]);
classNameAndIDs(1:2:end-1) = classNames;
classNameAndIDs(2:2:end) = typeIDs;
typeIDs = compose("<a href=""matlab:helpPopup %s"" style=""font-weight:bold"">%s</a>", classNameAndIDs);
end

text = names + ": " + typeIDs;
text = " " + strjoin(text, " | ");
end
25 changes: 18 additions & 7 deletions matlab/src/matlab/+arrow/+tabular/Schema.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,29 @@
end
end

methods (Access = private)
methods (Access=protected)

function str = toString(obj)
str = obj.Proxy.toString();
function header = getHeader(obj)
name = matlab.mixin.CustomDisplay.getClassNameForHeader(obj);
numFields = obj.NumFields;
if numFields == 0
header = compose(" Arrow %s with 0 fields" + newline, name);
elseif numFields == 1
header = compose(" Arrow %s with %d field:" + newline, name, numFields);
else
header = compose(" Arrow %s with %d fields:" + newline, name, numFields);
end
end

end
function displayScalarObject(obj)
disp(getHeader(obj));
numFields = obj.NumFields;

methods (Access=protected)
if numFields > 0
text = arrow.tabular.internal.displaySchema(obj);
disp(text + newline);
end

function displayScalarObject(obj)
disp(obj.toString());
end

end
Expand Down
63 changes: 63 additions & 0 deletions matlab/test/arrow/tabular/tSchema.m
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,70 @@ function TestIsEqualFalse(testCase)

% Compare schema to double
testCase.verifyFalse(isequal(schema4, 5));
end

function TestDisplaySchemaZeroFields(testCase)
import arrow.internal.test.display.makeLinkString

schema = arrow.schema(arrow.type.Field.empty(0, 0)); %#ok<NASGU>
classnameLink = makeLinkString(FullClassName="arrow.tabular.Schema",...
ClassName="Schema", BoldFont=true);
expectedDisplay = " Arrow " + classnameLink + " with 0 fields" + newline;
expectedDisplay = char(expectedDisplay + newline);
actualDisplay = evalc('disp(schema)');
testCase.verifyEqual(actualDisplay, char(expectedDisplay));
end

function TestDisplaySchemaOneField(testCase)
import arrow.internal.test.display.makeLinkString

schema = arrow.schema(arrow.field("TestField", arrow.boolean())); %#ok<NASGU>
classnameLink = makeLinkString(FullClassName="arrow.tabular.Schema",...
ClassName="Schema", BoldFont=true);
header = " Arrow " + classnameLink + " with 1 field:" + newline;
indent = " ";

if usejava("desktop")
type = makeLinkString(FullClassName="arrow.type.BooleanType", ...
ClassName="Boolean", BoldFont=true);
name = "<strong>TestField</strong>: ";
fieldLine = indent + name + type + newline;
else
fieldLine = indent + "TestField: Boolean" + newline;
end
expectedDisplay = join([header, fieldLine], newline);
expectedDisplay = char(expectedDisplay + newline);
actualDisplay = evalc('disp(schema)');
testCase.verifyEqual(actualDisplay, char(expectedDisplay));
end

function TestDisplaySchemaField(testCase)
import arrow.internal.test.display.makeLinkString

field1 = arrow.field("Field1", arrow.timestamp());
field2 = arrow.field("Field2", arrow.string());
schema = arrow.schema([field1, field2]); %#ok<NASGU>
classnameLink = makeLinkString(FullClassName="arrow.tabular.Schema",...
ClassName="Schema", BoldFont=true);
header = " Arrow " + classnameLink + " with 2 fields:" + newline;

indent = " ";
if usejava("desktop")
type1 = makeLinkString(FullClassName="arrow.type.TimestampType", ...
ClassName="Timestamp", BoldFont=true);
field1String = "<strong>Field1</strong>: " + type1;
type2 = makeLinkString(FullClassName="arrow.type.StringType", ...
ClassName="String", BoldFont=true);
field2String = "<strong>Field2</strong>: " + type2;
fieldLine = indent + field1String + " | " + field2String + newline;
else
fieldLine = indent + "Field1: Timestamp | Field2: String" + newline;
end

expectedDisplay = join([header, fieldLine], newline);
expectedDisplay = char(expectedDisplay + newline);
actualDisplay = evalc('disp(schema)');
testCase.verifyEqual(actualDisplay, char(expectedDisplay));
end

end
Expand Down

0 comments on commit 814151d

Please sign in to comment.