mirror of https://github.com/icedland/iced.git
Generate pyi docs
This commit is contained in:
parent
d5388ec9bd
commit
6ed7b7c006
|
@ -41,6 +41,7 @@ namespace Generator.Misc.Python {
|
|||
Args,
|
||||
}
|
||||
|
||||
[DebuggerDisplay("Count = {Attributes.Count}")]
|
||||
sealed class RustAttributes {
|
||||
public readonly List<RustAttribute> Attributes = new List<RustAttribute>();
|
||||
|
||||
|
@ -71,6 +72,7 @@ namespace Generator.Misc.Python {
|
|||
Args,
|
||||
Raises,
|
||||
Returns,
|
||||
Note,
|
||||
TestCode,
|
||||
TestOutput,
|
||||
}
|
||||
|
@ -132,6 +134,11 @@ namespace Generator.Misc.Python {
|
|||
public ReturnsDocCommentSection(TypeAndDocs returns) => Returns = returns;
|
||||
}
|
||||
|
||||
sealed class NoteDocCommentSection : DocCommentSection {
|
||||
public readonly string[] Lines;
|
||||
public NoteDocCommentSection(string[] lines) => Lines = lines;
|
||||
}
|
||||
|
||||
sealed class TestCodeDocCommentSection : DocCommentSection {
|
||||
public readonly string[] Lines;
|
||||
public TestCodeDocCommentSection(string[] lines) => Lines = lines;
|
||||
|
|
|
@ -713,6 +713,7 @@ namespace Generator.Misc.Python {
|
|||
"Args:" => DocCommentKind.Args,
|
||||
"Raises:" => DocCommentKind.Raises,
|
||||
"Returns:" => DocCommentKind.Returns,
|
||||
"Note:" => DocCommentKind.Note,
|
||||
".. testcode::" => DocCommentKind.TestCode,
|
||||
".. testoutput::" => DocCommentKind.TestOutput,
|
||||
_ => DocCommentKind.Text,
|
||||
|
@ -790,6 +791,12 @@ namespace Generator.Misc.Python {
|
|||
docs.Sections.Add(new ReturnsDocCommentSection(returns.Value));
|
||||
break;
|
||||
|
||||
case DocCommentKind.Note:
|
||||
if (!TryGetDescLines(lines, ref i, out descLines, out error))
|
||||
return false;
|
||||
docs.Sections.Add(new NoteDocCommentSection(descLines.ToArray()));
|
||||
break;
|
||||
|
||||
case DocCommentKind.TestCode:
|
||||
if (!TryReadExampleLines(lines, ref i, out var exampleLines, out error))
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
Copyright (C) 2018-2019 de4dot@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Generator.Misc.Python {
|
||||
// The docs in the *.rs files are in a format understood by sphinx but it's not
|
||||
// supported by most Python language servers. This class converts the docs to
|
||||
// something that is accepted by most Python language servers.
|
||||
//
|
||||
// Tested: VSCode(Jedi, Pylance, Microsoft)
|
||||
sealed class PyiDocGen {
|
||||
readonly List<string> converted = new List<string>();
|
||||
|
||||
const string HeaderPrefix = "###";
|
||||
|
||||
public List<string> Convert(DocComments docComments) {
|
||||
converted.Clear();
|
||||
|
||||
bool addEmptyLine = false;
|
||||
var colDefs = new List<(int start, int length)>();
|
||||
var tableLines = new List<List<string>>();
|
||||
var columnLengths = new List<int>();
|
||||
var sb = new StringBuilder();
|
||||
foreach (var section in docComments.Sections) {
|
||||
if (addEmptyLine)
|
||||
converted.Add(string.Empty);
|
||||
addEmptyLine = true;
|
||||
|
||||
switch (section) {
|
||||
case TextDocCommentSection text:
|
||||
for (int i = 0; i < text.Lines.Length; i++) {
|
||||
var line = text.Lines[i];
|
||||
|
||||
// Convert tables to code blocks. They're not converted to
|
||||
// markdown tables because Jedi doesn't support tables in
|
||||
// tooltips. Instead, we create a text block that will be
|
||||
// rendered with a mono spaced font.
|
||||
if (IsTableLine(line)) {
|
||||
GetColumnDefs(colDefs, line);
|
||||
if (colDefs.Count < 2)
|
||||
throw new InvalidOperationException($"Invalid table, expected at least 2 columns. First line: {line}");
|
||||
|
||||
i++;
|
||||
if (!TryGetNextTableLine(text.Lines, ref i, out var tblLine) || IsTableLine(tblLine))
|
||||
throw new InvalidOperationException("Invalid table");
|
||||
tableLines.Add(GetColumns(colDefs, tblLine));
|
||||
if (!TryGetNextTableLine(text.Lines, ref i, out tblLine) || !IsTableLine(tblLine))
|
||||
throw new InvalidOperationException("Invalid table");
|
||||
while (TryGetNextTableLine(text.Lines, ref i, out tblLine) && !IsTableLine(tblLine))
|
||||
tableLines.Add(GetColumns(colDefs, tblLine));
|
||||
|
||||
foreach (var tableCols in tableLines) {
|
||||
for (int j = 0; j < tableCols.Count; j++)
|
||||
tableCols[j] = FixTableColumn(tableCols[j], j == 0);
|
||||
}
|
||||
|
||||
columnLengths.Clear();
|
||||
for (int j = 0; j < colDefs.Count; j++)
|
||||
columnLengths.Add(0);
|
||||
foreach (var tableCols in tableLines) {
|
||||
if (tableCols.Count != columnLengths.Count)
|
||||
throw new InvalidOperationException();
|
||||
for (int j = 0; j < columnLengths.Count; j++)
|
||||
columnLengths[j] = Math.Max(columnLengths[j], tableCols[j].Length);
|
||||
}
|
||||
const int colSepLen = 2;
|
||||
for (int j = 0; j < columnLengths.Count - 1; j++)
|
||||
columnLengths[j] += colSepLen;
|
||||
|
||||
converted.Add("```text");
|
||||
for (int j = 0; j < tableLines.Count; j++) {
|
||||
var tableCols = tableLines[j];
|
||||
sb.Clear();
|
||||
for (int k = 0; k < tableCols.Count; k++) {
|
||||
var col = tableCols[k];
|
||||
sb.Append(FixCodeBlockLine(col));
|
||||
int colLen = columnLengths[k];
|
||||
if (col.Length > colLen)
|
||||
throw new InvalidOperationException();
|
||||
if (k + 1 != tableCols.Count)
|
||||
sb.Append(' ', colLen - col.Length);
|
||||
}
|
||||
converted.Add(sb.ToString());
|
||||
if (j == 0) {
|
||||
sb.Clear();
|
||||
sb.Append('-', columnLengths.Sum() - columnLengths[^1] + tableLines[0][^1].Length);
|
||||
converted.Add(sb.ToString());
|
||||
}
|
||||
}
|
||||
converted.Add("```");
|
||||
}
|
||||
else
|
||||
converted.Add(FixDocLine(line));
|
||||
}
|
||||
break;
|
||||
|
||||
case ArgsDocCommentSection args:
|
||||
converted.Add($"{HeaderPrefix} Args:");
|
||||
converted.Add(string.Empty);
|
||||
foreach (var arg in args.Args)
|
||||
converted.Add(FixDocLine($"- `{arg.Name}` ({arg.SphinxType}): {arg.Documentation}"));
|
||||
break;
|
||||
|
||||
case RaisesDocCommentSection raises:
|
||||
converted.Add($"{HeaderPrefix} Raises:");
|
||||
converted.Add(string.Empty);
|
||||
foreach (var raise in raises.Raises)
|
||||
converted.Add(FixDocLine($"- {raise.SphinxType}: {raise.Documentation}"));
|
||||
break;
|
||||
|
||||
case ReturnsDocCommentSection returns:
|
||||
converted.Add($"{HeaderPrefix} Returns:");
|
||||
converted.Add(string.Empty);
|
||||
converted.Add(FixDocLine($"- {returns.Returns.SphinxType}: {returns.Returns.Documentation}"));
|
||||
break;
|
||||
|
||||
case NoteDocCommentSection note:
|
||||
converted.Add($"{HeaderPrefix} Note:");
|
||||
converted.Add(string.Empty);
|
||||
foreach (var line in note.Lines)
|
||||
converted.Add(FixDocLine($"- {line}"));
|
||||
break;
|
||||
|
||||
case TestCodeDocCommentSection testCode:
|
||||
converted.Add("```python");
|
||||
foreach (var line in testCode.Lines)
|
||||
converted.Add(FixCodeBlockLine(line));
|
||||
converted.Add("```");
|
||||
break;
|
||||
|
||||
case TestOutputDocCommentSection testOutput:
|
||||
converted.Add("```text");
|
||||
foreach (var line in testOutput.Lines)
|
||||
converted.Add(FixCodeBlockLine(line));
|
||||
converted.Add("```");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
static bool IsTableLine(string line) =>
|
||||
line.StartsWith("===", StringComparison.Ordinal) &&
|
||||
line.EndsWith("===", StringComparison.Ordinal);
|
||||
|
||||
static bool TryGetNextTableLine(string[] strings, ref int i, [NotNullWhen(true)] out string? tblLine) {
|
||||
if (i >= strings.Length) {
|
||||
tblLine = null;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
tblLine = strings[i];
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static List<string> GetColumns(List<(int start, int length)> colDefs, string line) {
|
||||
var cols = new List<string>();
|
||||
for (int i = 0; i < colDefs.Count; i++) {
|
||||
bool isLastCol = i + 1 == colDefs.Count;
|
||||
var colDef = colDefs[i];
|
||||
if (colDef.start > 0 && line[colDef.start - 1] != ' ')
|
||||
throw new InvalidOperationException($"Invalid column #{i}, line: {line}");
|
||||
if (!isLastCol &&
|
||||
colDef.start + colDef.length < line.Length && line[colDef.start + colDef.length] != ' ') {
|
||||
throw new InvalidOperationException($"Invalid column #{i}, line: {line}");
|
||||
}
|
||||
string col;
|
||||
if (isLastCol)
|
||||
col = line.Substring(colDef.start).Trim();
|
||||
else
|
||||
col = line.Substring(colDef.start, colDef.length).Trim();
|
||||
cols.Add(col);
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
static void GetColumnDefs(List<(int start, int length)> colDefs, string line) {
|
||||
colDefs.Clear();
|
||||
for (int index = 0; ;) {
|
||||
while (index < line.Length && line[index] == ' ')
|
||||
index++;
|
||||
if (index >= line.Length)
|
||||
break;
|
||||
int startIndex = index;
|
||||
index = line.IndexOf(' ', index);
|
||||
if (index < 0)
|
||||
index = line.Length;
|
||||
colDefs.Add((startIndex, index - startIndex));
|
||||
}
|
||||
}
|
||||
|
||||
static string FixDocLine(string line) {
|
||||
line = line.Replace(@"\", @"\\");
|
||||
line = line.Replace("\"\"\"", "\\\"\\\"\\\"");
|
||||
line = line.Replace(@"``", @"`");
|
||||
line = line.Replace(@":class:", string.Empty);
|
||||
if (line == "Examples:")
|
||||
line = HeaderPrefix + " " + line;
|
||||
return line;
|
||||
}
|
||||
|
||||
static string FixCodeBlockLine(string line) {
|
||||
line = line.Replace(@"\", @"\\");
|
||||
line = line.Replace("\"\"\"", "\\\"\\\"\\\"");
|
||||
return line;
|
||||
}
|
||||
|
||||
static string FixTableColumn(string line, bool isFirst) {
|
||||
if (isFirst && line == @"\")
|
||||
line = string.Empty;
|
||||
line = line.Replace(@"``", @"`");
|
||||
line = line.Replace(@":class:", string.Empty);
|
||||
return line;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -169,26 +169,29 @@ namespace Generator.Misc.Python {
|
|||
writer.WriteLine($"class {pythonName}({baseClass}): ...");
|
||||
}
|
||||
|
||||
var docGen = new PyiDocGen();
|
||||
foreach (var classStr in classOrder) {
|
||||
var pyClass = toClass[classStr];
|
||||
toClass.Remove(classStr);
|
||||
writer.WriteLine();
|
||||
writer.WriteLine($"class {idConverter.Type(pyClass.Name)}:");
|
||||
using (writer.Indent()) {
|
||||
WriteDocs(writer, docGen.Convert(pyClass.DocComments));
|
||||
|
||||
int defCount = 0;
|
||||
foreach (var member in GetMembers(pyClass)) {
|
||||
switch (member) {
|
||||
case PyMethod method:
|
||||
var docComments = method.Attributes.Any(AttributeKind.New) == true ?
|
||||
pyClass.DocComments : method.DocComments;
|
||||
Write(writer, idConverter, pyClass, method, docComments, toEnumType);
|
||||
Write(writer, docGen, idConverter, pyClass, method, docComments, toEnumType);
|
||||
defCount++;
|
||||
break;
|
||||
case PyProperty property:
|
||||
Write(writer, idConverter, pyClass, property.Getter, property.Getter.DocComments, toEnumType);
|
||||
Write(writer, docGen, idConverter, pyClass, property.Getter, property.Getter.DocComments, toEnumType);
|
||||
defCount++;
|
||||
if (property.Setter is not null) {
|
||||
Write(writer, idConverter, pyClass, property.Setter, property.Getter.DocComments, toEnumType);
|
||||
Write(writer, docGen, idConverter, pyClass, property.Setter, property.Getter.DocComments, toEnumType);
|
||||
defCount++;
|
||||
}
|
||||
break;
|
||||
|
@ -205,7 +208,26 @@ namespace Generator.Misc.Python {
|
|||
}
|
||||
}
|
||||
|
||||
static void Write(FileWriter writer, IdentifierConverter idConverter, PyClass pyClass, PyMethod method, DocComments docComments, Dictionary<string, EnumType> toEnumType) {
|
||||
static void WriteDocs(FileWriter writer, List<string> docs) {
|
||||
if (docs.Count == 0)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
const string docQuotes = "\"\"\"";
|
||||
if (docs.Count == 1)
|
||||
writer.WriteLine($"{docQuotes}{docs[0]}{docQuotes}");
|
||||
else {
|
||||
writer.WriteLine(docQuotes);
|
||||
foreach (var doc in docs) {
|
||||
if (doc == string.Empty)
|
||||
writer.WriteLineNoIndent(string.Empty);
|
||||
else
|
||||
writer.WriteLine(doc);
|
||||
}
|
||||
writer.WriteLine(docQuotes);
|
||||
}
|
||||
}
|
||||
|
||||
static void Write(FileWriter writer, PyiDocGen docGen, IdentifierConverter idConverter, PyClass pyClass, PyMethod method, DocComments docComments, Dictionary<string, EnumType> toEnumType) {
|
||||
if (method.Attributes.Any(AttributeKind.ClassMethod) == true)
|
||||
writer.WriteLine("@classmethod");
|
||||
if (method.Attributes.Any(AttributeKind.StaticMethod) == true)
|
||||
|
@ -291,7 +313,15 @@ namespace Generator.Misc.Python {
|
|||
writer.Write(GetReturnType(pyClass, method.Name, method.RustReturnType, sphinxReturnType));
|
||||
else
|
||||
writer.Write("None");
|
||||
writer.WriteLine(": ...");
|
||||
if (method.DocComments.Sections.Count == 0)
|
||||
writer.WriteLine(": ...");
|
||||
else {
|
||||
writer.WriteLine(":");
|
||||
using (writer.Indent()) {
|
||||
WriteDocs(writer, docGen.Convert(method.DocComments));
|
||||
writer.WriteLine("...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryGetValueStr(IdentifierConverter idConverter, string typeStr, string defaultValueStr, Dictionary<string, EnumType> toEnumType, [NotNullWhen(true)] out string? valueStr) {
|
||||
|
@ -395,7 +425,7 @@ namespace Generator.Misc.Python {
|
|||
}
|
||||
}
|
||||
|
||||
IEnumerable<object> GetMembers(PyClass pyClass) {
|
||||
static IEnumerable<object> GetMembers(PyClass pyClass) {
|
||||
var setters = pyClass.Methods.Where(a => a.Attributes.Any(AttributeKind.Setter) == true).ToDictionary(a => a.Name, a => a, StringComparer.Ordinal);
|
||||
foreach (var method in pyClass.Methods) {
|
||||
if (method.Attributes.Any(AttributeKind.Setter) == true)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -363,8 +363,7 @@ impl Instruction {
|
|||
/// You can also call `len(instr)` to get this value.
|
||||
///
|
||||
/// Note:
|
||||
/// This is just informational. If you modify the instruction or create a new one,
|
||||
/// this method could return the wrong value.
|
||||
/// This is just informational. If you modify the instruction or create a new one, this method could return the wrong value.
|
||||
#[getter]
|
||||
fn len(&self) -> usize {
|
||||
self.instr.len()
|
||||
|
|
Loading…
Reference in New Issue