mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-11-15 13:26:32 +00:00
Move docs generator into the telethon_generator package
This commit is contained in:
44
telethon_generator/data/html/404.html
Normal file
44
telethon_generator/data/html/404.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<title>Oopsie! | Telethon</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #f0f4f8;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
div {
|
||||
width: 560px;
|
||||
margin: 5em auto;
|
||||
padding: 50px;
|
||||
background-color: #fff;
|
||||
border-radius: 1em;
|
||||
}
|
||||
a:link, a:visited {
|
||||
color: #38488f;
|
||||
text-decoration: none;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
div {
|
||||
width: auto;
|
||||
margin: 0 auto;
|
||||
border-radius: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>You seem a bit lost…</h1>
|
||||
<p>You seem to be lost! Don't worry, that's just Telegram's API being
|
||||
itself. Shall we go back to the <a href="index.html">Main Page</a>?</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
149
telethon_generator/data/html/core.html
Normal file
149
telethon_generator/data/html/core.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Telethon API</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="css/docs.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
overflow: scroll;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main_div">
|
||||
<noscript>Please enable JavaScript if you would like to use search.</noscript>
|
||||
<h1>Telethon API</h1>
|
||||
<p>This documentation was generated straight from the <code>scheme.tl</code>
|
||||
provided by Telegram. However, there is no official documentation per se
|
||||
on what the methods, constructors and types mean. Nevertheless, this
|
||||
page aims to provide easy access to all the available methods, their
|
||||
definition and parameters.</p>
|
||||
|
||||
<p>Please note that when you see this:</p>
|
||||
<pre>---functions---
|
||||
users.getUsers#0d91a548 id:Vector<InputUser> = Vector<User></pre>
|
||||
|
||||
<p>This is <b>not</b> Python code. It's the "TL definition". It's
|
||||
an easy-to-read line that gives a quick overview on the parameters
|
||||
and its result. You don't need to worry about this. See
|
||||
<a href="http://telethon.readthedocs.io/en/latest/extra/developing/understanding-the-type-language.html">here</a>
|
||||
for more details on it.</p>
|
||||
|
||||
<h3>Index</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#methods">Methods</a>
|
||||
(<a href="methods/index.html">full list</a>)
|
||||
</li>
|
||||
<li>
|
||||
<a href="#types">Types</a>
|
||||
(<a href="types/index.html">full list</a>)
|
||||
</li>
|
||||
<li>
|
||||
<a href="#constructors">Constructors</a>
|
||||
(<a href="constructors/index.html">full list</a>)
|
||||
</li>
|
||||
<li><a href="#core">Core types</a></li>
|
||||
<li><a href="#example">Full example</a></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="methods">Methods</h3>
|
||||
<p>Currently there are <b>{method_count} methods</b> available for the layer
|
||||
{layer}. The complete list can be seen <a href="methods/index.html">here</a>.
|
||||
<br /><br />
|
||||
Methods, also known as <i>requests</i>, are used to interact with the
|
||||
Telegram API itself and are invoked through <code>client(Request(...))</code>.
|
||||
<b>Only these</b> can be used like that! You cannot invoke types or
|
||||
constructors, only requests. After this, Telegram will return a
|
||||
<code>result</code>, which may be, for instance, a bunch of messages,
|
||||
some dialogs, users, etc.</p>
|
||||
|
||||
<h3 id="types">Types</h3>
|
||||
<p>Currently there are <b>{type_count} types</b>. You can see the full
|
||||
list <a href="types/index.html">here</a>.</p>
|
||||
|
||||
<p>The Telegram types are the <i>abstract</i> results that you receive
|
||||
after invoking a request. They are "abstract" because they can have
|
||||
multiple constructors. For instance, the abstract type <code>User</code>
|
||||
can be either <code>UserEmpty</code> or <code>User</code>. You should,
|
||||
most of the time, make sure you received the desired type by using
|
||||
the <code>isinstance(result, Constructor)</code> Python function.
|
||||
|
||||
When a request needs a Telegram type as argument, you should create
|
||||
an instance of it by using one of its, possibly multiple, constructors.</p>
|
||||
|
||||
<h3 id="constructors">Constructors</h3>
|
||||
<p>Currently there are <b>{constructor_count} constructors</b>. You can see
|
||||
the full list <a href="constructors/index.html">here</a>.</p>
|
||||
|
||||
<p>Constructors are the way you can create instances of the abstract types
|
||||
described above, and also the instances which are actually returned from
|
||||
the functions although they all share a common abstract type.</p>
|
||||
|
||||
<h3 id="core">Core types</h3>
|
||||
<p>Core types are types from which the rest of Telegram types build upon:</p>
|
||||
<ul>
|
||||
<li id="int"><b>int</b>:
|
||||
The value should be an integer type, like <span class="sh1">42</span>.
|
||||
It should have 32 bits or less. You can check the bit length by
|
||||
calling <code>a.bit_length()</code>, where <code>a</code> is an
|
||||
integer variable.
|
||||
</li>
|
||||
<li id="long"><b>long</b>:
|
||||
Different name for an integer type. The numbers given should have
|
||||
64 bits or less.
|
||||
</li>
|
||||
<li id="int128"><b>int128</b>:
|
||||
Another integer type, should have 128 bits or less.
|
||||
</li>
|
||||
<li id="int256"><b>int256</b>:
|
||||
The largest integer type, allowing 256 bits or less.
|
||||
</li>
|
||||
<li id="double"><b>double</b>:
|
||||
The value should be a floating point value, such as
|
||||
<span class="sh1">123.456</span>.
|
||||
</li>
|
||||
<li id="vector"><b>Vector<T></b>:
|
||||
If a type <code>T</code> is wrapped around <code>Vector<T></code>,
|
||||
then it means that the argument should be a <i>list</i> of it.
|
||||
For instance, a valid value for <code>Vector<int></code>
|
||||
would be <code>[1, 2, 3]</code>.
|
||||
</li>
|
||||
<li id="string"><b>string</b>:
|
||||
A valid UTF-8 string should be supplied. This is right how
|
||||
Python strings work, no further encoding is required.
|
||||
</li>
|
||||
<li id="bool"><b>Bool</b>:
|
||||
Either <code>True</code> or <code>False</code>.
|
||||
</li>
|
||||
<li id="true"><b>true</b>:
|
||||
These arguments aren't actually sent but rather encoded as flags.
|
||||
Any truthy value (<code>True</code>, <code>7</code>) will enable
|
||||
this flag, although it's recommended to use <code>True</code> or
|
||||
<code>None</code> to symbolize that it's not present.
|
||||
</li>
|
||||
<li id="bytes"><b>bytes</b>:
|
||||
A sequence of bytes, like <code>b'hello'</code>, should be supplied.
|
||||
</li>
|
||||
<li id="date"><b>date</b>:
|
||||
Although this type is internally used as an <code>int</code>,
|
||||
you can pass a <code>datetime</code> or <code>date</code> object
|
||||
instead to work with date parameters.<br />
|
||||
Note that the library uses the date in <b>UTC+0</b>, since timezone
|
||||
conversion is not responsibility of the library. Furthermore, this
|
||||
eases converting into any other timezone without the need for a middle
|
||||
step.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="example">Full example</h3>
|
||||
<p>Documentation for this is now
|
||||
<a href="http://telethon.readthedocs.io/en/latest/extra/advanced-usage/accessing-the-full-api.html">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
<script src="js/search.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
195
telethon_generator/data/html/css/docs.css
Normal file
195
telethon_generator/data/html/css/docs.css
Normal file
@@ -0,0 +1,195 @@
|
||||
body {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
color: #333;
|
||||
background-color:#fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #42aaed;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
padding: 8px;
|
||||
color: #567;
|
||||
background: #f0f4f8;
|
||||
border-radius: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #64bbdd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-top: 1px solid #eee;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
margin-bottom: 16px;
|
||||
list-style: none;
|
||||
background: #f0f4f8;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.horizontal li {
|
||||
display: inline-block;
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.horizontal img {
|
||||
display: inline-block;
|
||||
margin: 0 8px -2px 0;
|
||||
}
|
||||
|
||||
h1, summary.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#main_div {
|
||||
padding: 20px 0;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-track:horizontal {
|
||||
background: #def;
|
||||
border-radius: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-thumb:horizontal {
|
||||
background: #bdd;
|
||||
border-radius: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
:target {
|
||||
border: 2px solid #f8f800;
|
||||
background: #f8f8f8;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* 'sh' stands for Syntax Highlight */
|
||||
span.sh1 {
|
||||
color: #f70;
|
||||
}
|
||||
|
||||
span.sh2 {
|
||||
color: #0c7;
|
||||
}
|
||||
|
||||
span.sh3 {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.sh4 {
|
||||
color: #06c;
|
||||
}
|
||||
|
||||
span.tooltip {
|
||||
border-bottom: 1px dashed #444;
|
||||
}
|
||||
|
||||
#searchBox {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 20px;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 2px;
|
||||
border: 2px solid #ddd;
|
||||
}
|
||||
|
||||
#searchBox:placeholder-shown {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 2px;
|
||||
font-size: 16px;
|
||||
padding: 8px;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: 2px solid #42aaed;
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #42aaed;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* https://www.w3schools.com/css/css_navbar.asp */
|
||||
ul.together {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul.together li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul.together li a {
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
background: #f0f4f8;
|
||||
padding: 4px 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/a/30810322 */
|
||||
.invisible {
|
||||
left: 0;
|
||||
top: -99px;
|
||||
padding: 0;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: fixed;
|
||||
box-shadow: none;
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1, summary.title {
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#dev_page_content_wrap {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
#dev_page_title {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
35
telethon_generator/data/html/img/arrow.svg
Normal file
35
telethon_generator/data/html/img/arrow.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
viewBox="0 0 9 15"
|
||||
height="15"
|
||||
width="9">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-1037.3622)"
|
||||
id="layer1">
|
||||
<path
|
||||
id="path4163"
|
||||
d="m 0.1049,1039.6602 5.34527,5.0641 -5.43588,5.4358 1.81196,1.812 7.15946,-7.1594 -7.0291,-7.0604 z"
|
||||
style="fill:#42aaed;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
208
telethon_generator/data/html/js/search.js
Normal file
208
telethon_generator/data/html/js/search.js
Normal file
@@ -0,0 +1,208 @@
|
||||
root = document.getElementById("main_div");
|
||||
root.innerHTML = `
|
||||
<!-- You can append '?q=query' to the URL to default to a search -->
|
||||
<input id="searchBox" type="text" onkeyup="updateSearch()"
|
||||
placeholder="Search for requests and types…" />
|
||||
|
||||
<div id="searchDiv">
|
||||
<div id="exactMatch" style="display:none;">
|
||||
<b>Exact match:</b>
|
||||
<ul id="exactList" class="together">
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<details open><summary class="title">Methods (<span id="methodsCount">0</span>)</summary>
|
||||
<ul id="methodsList" class="together">
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details open><summary class="title">Types (<span id="typesCount">0</span>)</summary>
|
||||
<ul id="typesList" class="together">
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
<details><summary class="title">Constructors (<span id="constructorsCount">0</span>)</summary>
|
||||
<ul id="constructorsList" class="together">
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
<div id="contentDiv">
|
||||
` + root.innerHTML + "</div>";
|
||||
|
||||
// HTML modified, now load documents
|
||||
contentDiv = document.getElementById("contentDiv");
|
||||
searchDiv = document.getElementById("searchDiv");
|
||||
searchBox = document.getElementById("searchBox");
|
||||
|
||||
// Search lists
|
||||
methodsList = document.getElementById("methodsList");
|
||||
methodsCount = document.getElementById("methodsCount");
|
||||
|
||||
typesList = document.getElementById("typesList");
|
||||
typesCount = document.getElementById("typesCount");
|
||||
|
||||
constructorsList = document.getElementById("constructorsList");
|
||||
constructorsCount = document.getElementById("constructorsCount");
|
||||
|
||||
// Exact match
|
||||
exactMatch = document.getElementById("exactMatch");
|
||||
exactList = document.getElementById("exactList");
|
||||
|
||||
try {
|
||||
requests = [{request_names}];
|
||||
types = [{type_names}];
|
||||
constructors = [{constructor_names}];
|
||||
|
||||
requestsu = [{request_urls}];
|
||||
typesu = [{type_urls}];
|
||||
constructorsu = [{constructor_urls}];
|
||||
} catch (e) {
|
||||
requests = [];
|
||||
types = [];
|
||||
constructors = [];
|
||||
requestsu = [];
|
||||
typesu = [];
|
||||
constructorsu = [];
|
||||
}
|
||||
|
||||
if (typeof prependPath !== 'undefined') {
|
||||
for (var i = 0; i != requestsu.length; ++i) {
|
||||
requestsu[i] = prependPath + requestsu[i];
|
||||
}
|
||||
for (var i = 0; i != typesu.length; ++i) {
|
||||
typesu[i] = prependPath + typesu[i];
|
||||
}
|
||||
for (var i = 0; i != constructorsu.length; ++i) {
|
||||
constructorsu[i] = prependPath + constructorsu[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes haystack has no whitespace and both are lowercase.
|
||||
function find(haystack, needle) {
|
||||
if (needle.length == 0) {
|
||||
return true;
|
||||
}
|
||||
var hi = 0;
|
||||
var ni = 0;
|
||||
while (true) {
|
||||
while (needle[ni] < 'a' || needle[ni] > 'z') {
|
||||
++ni;
|
||||
if (ni == needle.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
while (haystack[hi] != needle[ni]) {
|
||||
++hi;
|
||||
if (hi == haystack.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++hi;
|
||||
++ni;
|
||||
if (ni == needle.length) {
|
||||
return true;
|
||||
}
|
||||
if (hi == haystack.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Given two input arrays "original" and "original urls" and a query,
|
||||
// return a pair of arrays with matching "query" elements from "original".
|
||||
//
|
||||
// TODO Perhaps return an array of pairs instead a pair of arrays (for cache).
|
||||
function getSearchArray(original, originalu, query) {
|
||||
var destination = [];
|
||||
var destinationu = [];
|
||||
|
||||
for (var i = 0; i < original.length; ++i) {
|
||||
if (find(original[i].toLowerCase(), query)) {
|
||||
destination.push(original[i]);
|
||||
destinationu.push(originalu[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return [destination, destinationu];
|
||||
}
|
||||
|
||||
// Modify "countSpan" and "resultList" accordingly based on the elements
|
||||
// given as [[elements], [element urls]] (both with the same length)
|
||||
function buildList(countSpan, resultList, foundElements) {
|
||||
var result = "";
|
||||
for (var i = 0; i < foundElements[0].length; ++i) {
|
||||
result += '<li>';
|
||||
result += '<a href="' + foundElements[1][i] + '">';
|
||||
result += foundElements[0][i];
|
||||
result += '</a></li>';
|
||||
}
|
||||
|
||||
if (countSpan) {
|
||||
countSpan.innerHTML = "" + foundElements[0].length;
|
||||
}
|
||||
resultList.innerHTML = result;
|
||||
}
|
||||
|
||||
function updateSearch() {
|
||||
if (searchBox.value) {
|
||||
contentDiv.style.display = "none";
|
||||
searchDiv.style.display = "";
|
||||
|
||||
var query = searchBox.value.toLowerCase();
|
||||
|
||||
var foundRequests = getSearchArray(requests, requestsu, query);
|
||||
var foundTypes = getSearchArray(types, typesu, query);
|
||||
var foundConstructors = getSearchArray(
|
||||
constructors, constructorsu, query
|
||||
);
|
||||
|
||||
buildList(methodsCount, methodsList, foundRequests);
|
||||
buildList(typesCount, typesList, foundTypes);
|
||||
buildList(constructorsCount, constructorsList, foundConstructors);
|
||||
|
||||
// Now look for exact matches
|
||||
var original = requests.concat(constructors);
|
||||
var originalu = requestsu.concat(constructorsu);
|
||||
var destination = [];
|
||||
var destinationu = [];
|
||||
|
||||
for (var i = 0; i < original.length; ++i) {
|
||||
if (original[i].toLowerCase().replace("request", "") == query) {
|
||||
destination.push(original[i]);
|
||||
destinationu.push(originalu[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (destination.length == 0) {
|
||||
exactMatch.style.display = "none";
|
||||
} else {
|
||||
exactMatch.style.display = "";
|
||||
buildList(null, exactList, [destination, destinationu]);
|
||||
return destinationu[0];
|
||||
}
|
||||
} else {
|
||||
contentDiv.style.display = "";
|
||||
searchDiv.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function getQuery(name) {
|
||||
var query = window.location.search.substring(1);
|
||||
var vars = query.split("&");
|
||||
for (var i = 0; i != vars.length; ++i) {
|
||||
var pair = vars[i].split("=");
|
||||
if (pair[0] == name)
|
||||
return pair[1];
|
||||
}
|
||||
}
|
||||
|
||||
var query = getQuery('q');
|
||||
if (query) {
|
||||
searchBox.value = query;
|
||||
}
|
||||
|
||||
var exactUrl = updateSearch();
|
||||
var redirect = getQuery('redirect');
|
||||
if (exactUrl && redirect != 'no') {
|
||||
window.location = exactUrl;
|
||||
}
|
||||
298
telethon_generator/docs_writer.py
Normal file
298
telethon_generator/docs_writer.py
Normal file
@@ -0,0 +1,298 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class DocsWriter:
|
||||
"""Utility class used to write the HTML files used on the documentation"""
|
||||
def __init__(self, filename, type_to_path_function):
|
||||
"""Initializes the writer to the specified output file,
|
||||
creating the parent directories when used if required.
|
||||
|
||||
'type_to_path_function' should be a function which, given a type
|
||||
name and a named argument relative_to, returns the file path for
|
||||
the specified type, relative to the given filename
|
||||
"""
|
||||
self.filename = filename
|
||||
self.handle = None
|
||||
|
||||
# Should be set before calling adding items to the menu
|
||||
self.menu_separator_tag = None
|
||||
|
||||
# Utility functions TODO There must be a better way
|
||||
self.type_to_path = lambda t: type_to_path_function(
|
||||
t, relative_to=self.filename
|
||||
)
|
||||
|
||||
# Control signals
|
||||
self.menu_began = False
|
||||
self.table_columns = 0
|
||||
self.table_columns_left = None
|
||||
self.write_copy_script = False
|
||||
self._script = ''
|
||||
|
||||
# High level writing
|
||||
def write_head(self, title, relative_css_path):
|
||||
"""Writes the head part for the generated document,
|
||||
with the given title and CSS
|
||||
"""
|
||||
self.write('''<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>''')
|
||||
|
||||
self.write(title)
|
||||
|
||||
self.write('''</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="''')
|
||||
|
||||
self.write(relative_css_path)
|
||||
|
||||
self.write('''" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main_div">''')
|
||||
|
||||
def set_menu_separator(self, relative_image_path):
|
||||
"""Sets the menu separator.
|
||||
Must be called before adding entries to the menu
|
||||
"""
|
||||
if relative_image_path:
|
||||
self.menu_separator_tag = \
|
||||
'<img src="{}" alt="/" />'.format(relative_image_path)
|
||||
else:
|
||||
self.menu_separator_tag = None
|
||||
|
||||
def add_menu(self, name, link=None):
|
||||
"""Adds a menu entry, will create it if it doesn't exist yet"""
|
||||
if self.menu_began:
|
||||
if self.menu_separator_tag:
|
||||
self.write(self.menu_separator_tag)
|
||||
else:
|
||||
# First time, create the menu tag
|
||||
self.write('<ul class="horizontal">')
|
||||
self.menu_began = True
|
||||
|
||||
self.write('<li>')
|
||||
if link:
|
||||
self.write('<a href="')
|
||||
self.write(link)
|
||||
self.write('">')
|
||||
|
||||
# Write the real menu entry text
|
||||
self.write(name)
|
||||
|
||||
if link:
|
||||
self.write('</a>')
|
||||
self.write('</li>')
|
||||
|
||||
def end_menu(self):
|
||||
"""Ends an opened menu"""
|
||||
if not self.menu_began:
|
||||
raise RuntimeError('No menu had been started in the first place.')
|
||||
self.write('</ul>')
|
||||
|
||||
def write_title(self, title, level=1):
|
||||
"""Writes a title header in the document body,
|
||||
with an optional depth level
|
||||
"""
|
||||
self.write('<h%d>' % level)
|
||||
self.write(title)
|
||||
self.write('</h%d>' % level)
|
||||
|
||||
def write_code(self, tlobject):
|
||||
"""Writes the code for the given 'tlobject' properly
|
||||
formatted with hyperlinks
|
||||
"""
|
||||
self.write('<pre>---')
|
||||
self.write('functions' if tlobject.is_function else 'types')
|
||||
self.write('---\n')
|
||||
|
||||
# Write the function or type and its ID
|
||||
if tlobject.namespace:
|
||||
self.write(tlobject.namespace)
|
||||
self.write('.')
|
||||
|
||||
self.write(tlobject.name)
|
||||
self.write('#')
|
||||
self.write(hex(tlobject.id)[2:].rjust(8, '0'))
|
||||
|
||||
# Write all the arguments (or do nothing if there's none)
|
||||
for arg in tlobject.args:
|
||||
self.write(' ')
|
||||
add_link = not arg.generic_definition and not arg.is_generic
|
||||
|
||||
# "Opening" modifiers
|
||||
if arg.generic_definition:
|
||||
self.write('{')
|
||||
|
||||
# Argument name
|
||||
self.write(arg.name)
|
||||
self.write(':')
|
||||
|
||||
# "Opening" modifiers
|
||||
if arg.is_flag:
|
||||
self.write('flags.%d?' % arg.flag_index)
|
||||
|
||||
if arg.is_generic:
|
||||
self.write('!')
|
||||
|
||||
if arg.is_vector:
|
||||
self.write(
|
||||
'<a href="%s">Vector</a><' % self.type_to_path('vector')
|
||||
)
|
||||
|
||||
# Argument type
|
||||
if arg.type:
|
||||
if add_link:
|
||||
self.write('<a href="%s">' % self.type_to_path(arg.type))
|
||||
self.write(arg.type)
|
||||
if add_link:
|
||||
self.write('</a>')
|
||||
else:
|
||||
self.write('#')
|
||||
|
||||
# "Closing" modifiers
|
||||
if arg.is_vector:
|
||||
self.write('>')
|
||||
|
||||
if arg.generic_definition:
|
||||
self.write('}')
|
||||
|
||||
# Now write the resulting type (result from a function/type)
|
||||
self.write(' = ')
|
||||
generic_name = next((arg.name for arg in tlobject.args
|
||||
if arg.generic_definition), None)
|
||||
|
||||
if tlobject.result == generic_name:
|
||||
# Generic results cannot have any link
|
||||
self.write(tlobject.result)
|
||||
else:
|
||||
if re.search('^vector<', tlobject.result, re.IGNORECASE):
|
||||
# Notice that we don't simply make up the "Vector" part,
|
||||
# because some requests (as of now, only FutureSalts),
|
||||
# use a lower type name for it (see #81)
|
||||
vector, inner = tlobject.result.split('<')
|
||||
inner = inner.strip('>')
|
||||
self.write('<a href="')
|
||||
self.write(self.type_to_path(vector))
|
||||
self.write('">%s</a><' % vector)
|
||||
|
||||
self.write('<a href="')
|
||||
self.write(self.type_to_path(inner))
|
||||
self.write('">%s</a>' % inner)
|
||||
|
||||
self.write('>')
|
||||
else:
|
||||
self.write('<a href="')
|
||||
self.write(self.type_to_path(tlobject.result))
|
||||
self.write('">%s</a>' % tlobject.result)
|
||||
|
||||
self.write('</pre>')
|
||||
|
||||
def begin_table(self, column_count):
|
||||
"""Begins a table with the given 'column_count', required to automatically
|
||||
create the right amount of columns when adding items to the rows"""
|
||||
self.table_columns = column_count
|
||||
self.table_columns_left = 0
|
||||
self.write('<table>')
|
||||
|
||||
def add_row(self, text, link=None, bold=False, align=None):
|
||||
"""This will create a new row, or add text to the next column
|
||||
of the previously created, incomplete row, closing it if complete"""
|
||||
if not self.table_columns_left:
|
||||
# Starting a new row
|
||||
self.write('<tr>')
|
||||
self.table_columns_left = self.table_columns
|
||||
|
||||
self.write('<td')
|
||||
if align:
|
||||
self.write(' style="text-align:')
|
||||
self.write(align)
|
||||
self.write('"')
|
||||
self.write('>')
|
||||
|
||||
if bold:
|
||||
self.write('<b>')
|
||||
if link:
|
||||
self.write('<a href="')
|
||||
self.write(link)
|
||||
self.write('">')
|
||||
|
||||
# Finally write the real table data, the given text
|
||||
self.write(text)
|
||||
|
||||
if link:
|
||||
self.write('</a>')
|
||||
if bold:
|
||||
self.write('</b>')
|
||||
|
||||
self.write('</td>')
|
||||
|
||||
self.table_columns_left -= 1
|
||||
if not self.table_columns_left:
|
||||
self.write('</tr>')
|
||||
|
||||
def end_table(self):
|
||||
# If there was any column left, finish it before closing the table
|
||||
if self.table_columns_left:
|
||||
self.write('</tr>')
|
||||
|
||||
self.write('</table>')
|
||||
|
||||
def write_text(self, text):
|
||||
"""Writes a paragraph of text"""
|
||||
self.write('<p>')
|
||||
self.write(text)
|
||||
self.write('</p>')
|
||||
|
||||
def write_copy_button(self, text, text_to_copy):
|
||||
"""Writes a button with 'text' which can be used
|
||||
to copy 'text_to_copy' to clipboard when it's clicked."""
|
||||
self.write_copy_script = True
|
||||
self.write('<button onclick="cp(\'{}\');">{}</button>'
|
||||
.format(text_to_copy, text))
|
||||
|
||||
def add_script(self, src='', relative_src=None):
|
||||
if relative_src:
|
||||
self._script += '<script src="{}"></script>'.format(relative_src)
|
||||
elif src:
|
||||
self._script += '<script>{}</script>'.format(src)
|
||||
|
||||
def end_body(self):
|
||||
"""Ends the whole document. This should be called the last"""
|
||||
if self.write_copy_script:
|
||||
self.write(
|
||||
'<textarea id="c" class="invisible"></textarea>'
|
||||
'<script>'
|
||||
'function cp(t){'
|
||||
'var c=document.getElementById("c");'
|
||||
'c.value=t;'
|
||||
'c.select();'
|
||||
'try{document.execCommand("copy")}'
|
||||
'catch(e){}}'
|
||||
'</script>')
|
||||
|
||||
self.write('</div>')
|
||||
self.write(self._script)
|
||||
self.write('</body></html>')
|
||||
|
||||
# "Low" level writing
|
||||
def write(self, s):
|
||||
"""Wrapper around handle.write"""
|
||||
self.handle.write(s)
|
||||
|
||||
# With block
|
||||
def __enter__(self):
|
||||
# Sanity check
|
||||
parent = os.path.dirname(self.filename)
|
||||
if parent:
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
|
||||
self.handle = open(self.filename, 'w', encoding='utf-8')
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.handle.close()
|
||||
607
telethon_generator/generators/docs.py
Executable file
607
telethon_generator/generators/docs.py
Executable file
@@ -0,0 +1,607 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import shutil
|
||||
try:
|
||||
from .docs_writer import DocsWriter
|
||||
except (ImportError, SystemError):
|
||||
from docs_writer import DocsWriter
|
||||
|
||||
# Small trick so importing telethon_generator works
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from telethon_generator.parser import TLParser, TLObject
|
||||
|
||||
|
||||
# TLObject -> Python class name
|
||||
def get_class_name(tlobject):
|
||||
"""Gets the class name following the Python style guidelines"""
|
||||
# Courtesy of http://stackoverflow.com/a/31531797/4759433
|
||||
name = tlobject.name if isinstance(tlobject, TLObject) else tlobject
|
||||
result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
|
||||
|
||||
# Replace '_' with '' once again to make sure it doesn't appear on the name
|
||||
result = result[:1].upper() + result[1:].replace('_', '')
|
||||
|
||||
# If it's a function, let it end with "Request" to identify them more easily
|
||||
if isinstance(tlobject, TLObject) and tlobject.is_function:
|
||||
result += 'Request'
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# TLObject -> filename
|
||||
def get_file_name(tlobject, add_extension=False):
|
||||
"""Gets the file name in file_name_format.html for the given TLObject.
|
||||
Only its name may also be given if the full TLObject is not available"""
|
||||
if isinstance(tlobject, TLObject):
|
||||
name = tlobject.name
|
||||
else:
|
||||
name = tlobject
|
||||
|
||||
# Courtesy of http://stackoverflow.com/a/1176023/4759433
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
result = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
if add_extension:
|
||||
return result + '.html'
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
# TLObject -> from ... import ...
|
||||
def get_import_code(tlobject):
|
||||
kind = 'functions' if tlobject.is_function else 'types'
|
||||
ns = '.' + tlobject.namespace if tlobject.namespace else ''
|
||||
|
||||
return 'from telethon.tl.{}{} import {}'\
|
||||
.format(kind, ns, get_class_name(tlobject))
|
||||
|
||||
|
||||
def get_create_path_for(tlobject):
|
||||
"""Gets the file path (and creates the parent directories)
|
||||
for the given 'tlobject', relative to nothing; only its local path"""
|
||||
|
||||
# Determine the output directory
|
||||
out_dir = 'methods' if tlobject.is_function else 'constructors'
|
||||
|
||||
if tlobject.namespace:
|
||||
out_dir = os.path.join(out_dir, tlobject.namespace)
|
||||
|
||||
# Ensure that it exists
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Return the resulting filename
|
||||
return os.path.join(out_dir, get_file_name(tlobject, add_extension=True))
|
||||
|
||||
|
||||
def is_core_type(type_):
|
||||
"""Returns "true" if the type is considered a core type"""
|
||||
return type_.lower() in {
|
||||
'int', 'long', 'int128', 'int256', 'double',
|
||||
'vector', 'string', 'bool', 'true', 'bytes', 'date'
|
||||
}
|
||||
|
||||
|
||||
def get_path_for_type(type_, relative_to='.'):
|
||||
"""Similar to getting the path for a TLObject, it might not be possible
|
||||
to have the TLObject itself but rather its name (the type);
|
||||
this method works in the same way, returning a relative path"""
|
||||
if is_core_type(type_):
|
||||
path = 'index.html#%s' % type_.lower()
|
||||
|
||||
elif '.' in type_:
|
||||
# If it's not a core type, then it has to be a custom Telegram type
|
||||
namespace, name = type_.split('.')
|
||||
path = 'types/%s/%s' % (namespace, get_file_name(name, True))
|
||||
else:
|
||||
path = 'types/%s' % get_file_name(type_, True)
|
||||
|
||||
return get_relative_path(path, relative_to)
|
||||
|
||||
|
||||
# Destination path from the current position -> relative to the given path
|
||||
def get_relative_path(destination, relative_to):
|
||||
if os.path.isfile(relative_to):
|
||||
relative_to = os.path.dirname(relative_to)
|
||||
|
||||
return os.path.relpath(destination, start=relative_to)
|
||||
|
||||
|
||||
def get_relative_paths(original, relative_to):
|
||||
"""Converts the dictionary of 'original' paths to relative paths
|
||||
starting from the given 'relative_to' file"""
|
||||
return {k: get_relative_path(v, relative_to) for k, v in original.items()}
|
||||
|
||||
|
||||
# Generate a index.html file for the given folder
|
||||
def find_title(html_file):
|
||||
"""Finds the <title> for the given HTML file, or (Unknown)"""
|
||||
with open(html_file) as handle:
|
||||
for line in handle:
|
||||
if '<title>' in line:
|
||||
# + 7 to skip len('<title>')
|
||||
return line[line.index('<title>') + 7:line.index('</title>')]
|
||||
|
||||
return '(Unknown)'
|
||||
|
||||
|
||||
def build_menu(docs, filename, relative_main_index):
|
||||
"""Builds the menu using the given DocumentWriter up to 'filename',
|
||||
which must be a file (it cannot be a directory)"""
|
||||
# TODO Maybe this could be part of DocsWriter itself, "build path menu"
|
||||
docs.add_menu('API', relative_main_index)
|
||||
|
||||
items = filename.split('/')
|
||||
for i in range(len(items) - 1):
|
||||
item = items[i]
|
||||
link = '../' * (len(items) - (i + 2))
|
||||
link += 'index.html'
|
||||
docs.add_menu(item.title(), link=link)
|
||||
|
||||
if items[-1] != 'index.html':
|
||||
docs.add_menu(os.path.splitext(items[-1])[0])
|
||||
docs.end_menu()
|
||||
|
||||
|
||||
def generate_index(folder, original_paths):
|
||||
"""Generates the index file for the specified folder"""
|
||||
|
||||
# Determine the namespaces listed here (as sub folders)
|
||||
# and the files (.html files) that we should link to
|
||||
namespaces = []
|
||||
files = []
|
||||
for item in os.listdir(folder):
|
||||
if os.path.isdir(os.path.join(folder, item)):
|
||||
namespaces.append(item)
|
||||
elif item != 'index.html':
|
||||
files.append(item)
|
||||
|
||||
# We work with relative paths
|
||||
paths = get_relative_paths(original_paths, relative_to=folder)
|
||||
|
||||
# Now that everything is setup, write the index.html file
|
||||
filename = os.path.join(folder, 'index.html')
|
||||
with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs:
|
||||
# Title should be the current folder name
|
||||
docs.write_head(folder.title(), relative_css_path=paths['css'])
|
||||
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
build_menu(docs, filename, relative_main_index=paths['index_all'])
|
||||
|
||||
docs.write_title(folder.title())
|
||||
|
||||
if namespaces:
|
||||
docs.write_title('Namespaces', level=3)
|
||||
docs.begin_table(4)
|
||||
namespaces.sort()
|
||||
for namespace in namespaces:
|
||||
# For every namespace, also write the index of it
|
||||
generate_index(os.path.join(folder, namespace), original_paths)
|
||||
docs.add_row(namespace.title(),
|
||||
link=os.path.join(namespace, 'index.html'))
|
||||
|
||||
docs.end_table()
|
||||
|
||||
docs.write_title('Available items')
|
||||
docs.begin_table(2)
|
||||
|
||||
files = [(f, find_title(os.path.join(folder, f))) for f in files]
|
||||
files.sort(key=lambda t: t[1])
|
||||
|
||||
for file, title in files:
|
||||
docs.add_row(title, link=file)
|
||||
|
||||
docs.end_table()
|
||||
docs.end_body()
|
||||
|
||||
|
||||
def get_description(arg):
|
||||
"""Generates a proper description for the given argument"""
|
||||
desc = []
|
||||
otherwise = False
|
||||
if arg.can_be_inferred:
|
||||
desc.append('If left unspecified, it will be inferred automatically.')
|
||||
otherwise = True
|
||||
elif arg.is_flag:
|
||||
desc.append('This argument can be omitted.')
|
||||
otherwise = True
|
||||
|
||||
if arg.type in {'InputPeer', 'InputUser', 'InputChannel'}:
|
||||
desc.append(
|
||||
'Anything entity-like will work if the library can find its '
|
||||
'<code>Input</code> version (e.g., usernames, <code>Peer</code>, '
|
||||
'<code>User</code> or <code>Channel</code> objects, etc.).'
|
||||
)
|
||||
|
||||
if arg.is_vector:
|
||||
if arg.is_generic:
|
||||
desc.append('A list of other Requests must be supplied.')
|
||||
else:
|
||||
desc.append('A list must be supplied.')
|
||||
elif arg.is_generic:
|
||||
desc.append('A different Request must be supplied for this argument.')
|
||||
else:
|
||||
otherwise = False # Always reset to false if no other text is added
|
||||
|
||||
if otherwise:
|
||||
desc.insert(1, 'Otherwise,')
|
||||
desc[-1] = desc[-1][:1].lower() + desc[-1][1:]
|
||||
|
||||
return ' '.join(desc).replace(
|
||||
'list',
|
||||
'<span class="tooltip" title="Any iterable that supports len() '
|
||||
'will work too">list</span>'
|
||||
)
|
||||
|
||||
|
||||
def copy_replace(src, dst, replacements):
|
||||
"""Copies the src file into dst applying the replacements dict"""
|
||||
with open(src) as infile, open(dst, 'w') as outfile:
|
||||
outfile.write(re.sub(
|
||||
'|'.join(re.escape(k) for k in replacements),
|
||||
lambda m: str(replacements[m.group(0)]),
|
||||
infile.read()
|
||||
))
|
||||
|
||||
|
||||
def generate_documentation(scheme_file):
|
||||
"""Generates the documentation HTML files from from scheme.tl to
|
||||
/methods and /constructors, etc.
|
||||
"""
|
||||
original_paths = {
|
||||
'css': 'css/docs.css',
|
||||
'arrow': 'img/arrow.svg',
|
||||
'search.js': 'js/search.js',
|
||||
'404': '404.html',
|
||||
'index_all': 'index.html',
|
||||
'index_types': 'types/index.html',
|
||||
'index_methods': 'methods/index.html',
|
||||
'index_constructors': 'constructors/index.html'
|
||||
}
|
||||
tlobjects = tuple(TLParser.parse_file(scheme_file))
|
||||
|
||||
print('Generating constructors and functions documentation...')
|
||||
|
||||
# Save 'Type: [Constructors]' for use in both:
|
||||
# * Seeing the return type or constructors belonging to the same type.
|
||||
# * Generating the types documentation, showing available constructors.
|
||||
# TODO Tried using 'defaultdict(list)' with strange results, make it work.
|
||||
tltypes = {}
|
||||
tlfunctions = {}
|
||||
for tlobject in tlobjects:
|
||||
# Select to which dictionary we want to store this type
|
||||
dictionary = tlfunctions if tlobject.is_function else tltypes
|
||||
|
||||
if tlobject.result in dictionary:
|
||||
dictionary[tlobject.result].append(tlobject)
|
||||
else:
|
||||
dictionary[tlobject.result] = [tlobject]
|
||||
|
||||
for tltype, constructors in tltypes.items():
|
||||
tltypes[tltype] = list(sorted(constructors, key=lambda c: c.name))
|
||||
|
||||
for tlobject in tlobjects:
|
||||
filename = get_create_path_for(tlobject)
|
||||
|
||||
# Determine the relative paths for this file
|
||||
paths = get_relative_paths(original_paths, relative_to=filename)
|
||||
|
||||
with DocsWriter(filename, type_to_path_function=get_path_for_type) \
|
||||
as docs:
|
||||
docs.write_head(
|
||||
title=get_class_name(tlobject),
|
||||
relative_css_path=paths['css'])
|
||||
|
||||
# Create the menu (path to the current TLObject)
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
build_menu(docs, filename, relative_main_index=paths['index_all'])
|
||||
|
||||
# Create the page title
|
||||
docs.write_title(get_class_name(tlobject))
|
||||
|
||||
# Write the code definition for this TLObject
|
||||
docs.write_code(tlobject)
|
||||
docs.write_copy_button('Copy import to the clipboard',
|
||||
get_import_code(tlobject))
|
||||
|
||||
# Write the return type (or constructors belonging to the same type)
|
||||
docs.write_title('Returns' if tlobject.is_function
|
||||
else 'Belongs to', level=3)
|
||||
|
||||
generic_arg = next((arg.name for arg in tlobject.args
|
||||
if arg.generic_definition), None)
|
||||
|
||||
if tlobject.result == generic_arg:
|
||||
# We assume it's a function returning a generic type
|
||||
generic_arg = next((arg.name for arg in tlobject.args
|
||||
if arg.is_generic))
|
||||
docs.write_text('This function returns the result of whatever '
|
||||
'the result from invoking the request passed '
|
||||
'through <i>{}</i> is.'.format(generic_arg))
|
||||
else:
|
||||
if re.search('^vector<', tlobject.result, re.IGNORECASE):
|
||||
docs.write_text('A list of the following type is returned.')
|
||||
_, inner = tlobject.result.split('<')
|
||||
inner = inner.strip('>')
|
||||
else:
|
||||
inner = tlobject.result
|
||||
|
||||
docs.begin_table(column_count=1)
|
||||
docs.add_row(inner, link=get_path_for_type(
|
||||
inner, relative_to=filename
|
||||
))
|
||||
docs.end_table()
|
||||
|
||||
constructors = tltypes.get(inner, [])
|
||||
if not constructors:
|
||||
docs.write_text('This type has no instances available.')
|
||||
elif len(constructors) == 1:
|
||||
docs.write_text('This type can only be an instance of:')
|
||||
else:
|
||||
docs.write_text('This type can be an instance of either:')
|
||||
|
||||
docs.begin_table(column_count=2)
|
||||
for constructor in constructors:
|
||||
link = get_create_path_for(constructor)
|
||||
link = get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(get_class_name(constructor), link=link)
|
||||
docs.end_table()
|
||||
|
||||
# Return (or similar types) written. Now parameters/members
|
||||
docs.write_title(
|
||||
'Parameters' if tlobject.is_function else 'Members', level=3
|
||||
)
|
||||
|
||||
# Sort the arguments in the same way they're sorted
|
||||
# on the generated code (flags go last)
|
||||
args = [
|
||||
a for a in tlobject.sorted_args()
|
||||
if not a.flag_indicator and not a.generic_definition
|
||||
]
|
||||
|
||||
if args:
|
||||
# Writing parameters
|
||||
docs.begin_table(column_count=3)
|
||||
|
||||
for arg in args:
|
||||
# Name row
|
||||
docs.add_row(arg.name,
|
||||
bold=True)
|
||||
|
||||
# Type row
|
||||
if arg.is_generic:
|
||||
docs.add_row('!' + arg.type, align='center')
|
||||
else:
|
||||
docs.add_row(
|
||||
arg.type, align='center', link=
|
||||
get_path_for_type(arg.type, relative_to=filename)
|
||||
)
|
||||
|
||||
# Add a description for this argument
|
||||
docs.add_row(get_description(arg))
|
||||
|
||||
docs.end_table()
|
||||
else:
|
||||
if tlobject.is_function:
|
||||
docs.write_text('This request takes no input parameters.')
|
||||
else:
|
||||
docs.write_text('This type has no members.')
|
||||
|
||||
# TODO Bit hacky, make everything like this? (prepending '../')
|
||||
depth = '../' * (2 if tlobject.namespace else 1)
|
||||
docs.add_script(src='prependPath = "{}";'.format(depth))
|
||||
docs.add_script(relative_src=paths['search.js'])
|
||||
docs.end_body()
|
||||
|
||||
# Find all the available types (which are not the same as the constructors)
|
||||
# Each type has a list of constructors associated to it, hence is a map
|
||||
print('Generating types documentation...')
|
||||
for tltype, constructors in tltypes.items():
|
||||
filename = get_path_for_type(tltype)
|
||||
out_dir = os.path.dirname(filename)
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Since we don't have access to the full TLObject, split the type
|
||||
if '.' in tltype:
|
||||
namespace, name = tltype.split('.')
|
||||
else:
|
||||
namespace, name = None, tltype
|
||||
|
||||
# Determine the relative paths for this file
|
||||
paths = get_relative_paths(original_paths, relative_to=out_dir)
|
||||
|
||||
with DocsWriter(filename, type_to_path_function=get_path_for_type) \
|
||||
as docs:
|
||||
docs.write_head(
|
||||
title=get_class_name(name),
|
||||
relative_css_path=paths['css'])
|
||||
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
build_menu(docs, filename, relative_main_index=paths['index_all'])
|
||||
|
||||
# Main file title
|
||||
docs.write_title(get_class_name(name))
|
||||
|
||||
# List available constructors for this type
|
||||
docs.write_title('Available constructors', level=3)
|
||||
if not constructors:
|
||||
docs.write_text('This type has no constructors available.')
|
||||
elif len(constructors) == 1:
|
||||
docs.write_text('This type has one constructor available.')
|
||||
else:
|
||||
docs.write_text('This type has %d constructors available.' %
|
||||
len(constructors))
|
||||
|
||||
docs.begin_table(2)
|
||||
for constructor in constructors:
|
||||
# Constructor full name
|
||||
link = get_create_path_for(constructor)
|
||||
link = get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(get_class_name(constructor), link=link)
|
||||
docs.end_table()
|
||||
|
||||
# List all the methods which return this type
|
||||
docs.write_title('Methods returning this type', level=3)
|
||||
functions = tlfunctions.get(tltype, [])
|
||||
if not functions:
|
||||
docs.write_text('No method returns this type.')
|
||||
elif len(functions) == 1:
|
||||
docs.write_text('Only the following method returns this type.')
|
||||
else:
|
||||
docs.write_text(
|
||||
'The following %d methods return this type as a result.' %
|
||||
len(functions)
|
||||
)
|
||||
|
||||
docs.begin_table(2)
|
||||
for func in functions:
|
||||
link = get_create_path_for(func)
|
||||
link = get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(get_class_name(func), link=link)
|
||||
docs.end_table()
|
||||
|
||||
# List all the methods which take this type as input
|
||||
docs.write_title('Methods accepting this type as input', level=3)
|
||||
other_methods = sorted(
|
||||
(t for t in tlobjects
|
||||
if any(tltype == a.type for a in t.args) and t.is_function),
|
||||
key=lambda t: t.name
|
||||
)
|
||||
if not other_methods:
|
||||
docs.write_text(
|
||||
'No methods accept this type as an input parameter.')
|
||||
elif len(other_methods) == 1:
|
||||
docs.write_text(
|
||||
'Only this method has a parameter with this type.')
|
||||
else:
|
||||
docs.write_text(
|
||||
'The following %d methods accept this type as an input '
|
||||
'parameter.' % len(other_methods))
|
||||
|
||||
docs.begin_table(2)
|
||||
for ot in other_methods:
|
||||
link = get_create_path_for(ot)
|
||||
link = get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(get_class_name(ot), link=link)
|
||||
docs.end_table()
|
||||
|
||||
# List every other type which has this type as a member
|
||||
docs.write_title('Other types containing this type', level=3)
|
||||
other_types = sorted(
|
||||
(t for t in tlobjects
|
||||
if any(tltype == a.type for a in t.args)
|
||||
and not t.is_function
|
||||
), key=lambda t: t.name
|
||||
)
|
||||
|
||||
if not other_types:
|
||||
docs.write_text(
|
||||
'No other types have a member of this type.')
|
||||
elif len(other_types) == 1:
|
||||
docs.write_text(
|
||||
'You can find this type as a member of this other type.')
|
||||
else:
|
||||
docs.write_text(
|
||||
'You can find this type as a member of any of '
|
||||
'the following %d types.' % len(other_types))
|
||||
|
||||
docs.begin_table(2)
|
||||
for ot in other_types:
|
||||
link = get_create_path_for(ot)
|
||||
link = get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(get_class_name(ot), link=link)
|
||||
docs.end_table()
|
||||
docs.end_body()
|
||||
|
||||
# After everything's been written, generate an index.html per folder.
|
||||
# This will be done automatically and not taking into account any extra
|
||||
# information that we have available, simply a file listing all the others
|
||||
# accessible by clicking on their title
|
||||
print('Generating indices...')
|
||||
for folder in ['types', 'methods', 'constructors']:
|
||||
generate_index(folder, original_paths)
|
||||
|
||||
# Write the final core index, the main index for the rest of files
|
||||
layer = TLParser.find_layer(scheme_file)
|
||||
types = set()
|
||||
methods = []
|
||||
constructors = []
|
||||
for tlobject in tlobjects:
|
||||
if tlobject.is_function:
|
||||
methods.append(tlobject)
|
||||
else:
|
||||
constructors.append(tlobject)
|
||||
|
||||
if not is_core_type(tlobject.result):
|
||||
if re.search('^vector<', tlobject.result, re.IGNORECASE):
|
||||
types.add(tlobject.result.split('<')[1].strip('>'))
|
||||
else:
|
||||
types.add(tlobject.result)
|
||||
|
||||
types = sorted(types)
|
||||
methods = sorted(methods, key=lambda m: m.name)
|
||||
constructors = sorted(constructors, key=lambda c: c.name)
|
||||
|
||||
def fmt(xs):
|
||||
ys = {x: get_class_name(x) for x in xs} # cache TLObject: display
|
||||
zs = {} # create a dict to hold those which have duplicated keys
|
||||
for y in ys.values():
|
||||
zs[y] = y in zs
|
||||
return ', '.join(
|
||||
'"{}.{}"'.format(x.namespace, ys[x])
|
||||
if zs[ys[x]] and getattr(x, 'namespace', None)
|
||||
else '"{}"'.format(ys[x]) for x in xs
|
||||
)
|
||||
|
||||
request_names = fmt(methods)
|
||||
type_names = fmt(types)
|
||||
constructor_names = fmt(constructors)
|
||||
|
||||
def fmt(xs, formatter):
|
||||
return ', '.join('"{}"'.format(formatter(x)) for x in xs)
|
||||
|
||||
request_urls = fmt(methods, get_create_path_for)
|
||||
type_urls = fmt(types, get_path_for_type)
|
||||
constructor_urls = fmt(constructors, get_create_path_for)
|
||||
|
||||
shutil.copy('../res/404.html', original_paths['404'])
|
||||
copy_replace('../res/core.html', original_paths['index_all'], {
|
||||
'{type_count}': len(types),
|
||||
'{method_count}': len(methods),
|
||||
'{constructor_count}': len(tlobjects) - len(methods),
|
||||
'{layer}': layer,
|
||||
})
|
||||
os.makedirs(os.path.abspath(os.path.join(
|
||||
original_paths['search.js'], os.path.pardir
|
||||
)), exist_ok=True)
|
||||
copy_replace('../res/js/search.js', original_paths['search.js'], {
|
||||
'{request_names}': request_names,
|
||||
'{type_names}': type_names,
|
||||
'{constructor_names}': constructor_names,
|
||||
'{request_urls}': request_urls,
|
||||
'{type_urls}': type_urls,
|
||||
'{constructor_urls}': constructor_urls
|
||||
})
|
||||
|
||||
# Everything done
|
||||
print('Documentation generated.')
|
||||
|
||||
|
||||
def copy_resources():
|
||||
for d in ('css', 'img'):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
shutil.copy('../res/img/arrow.svg', 'img')
|
||||
shutil.copy('../res/css/docs.css', 'css')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.makedirs('generated', exist_ok=True)
|
||||
os.chdir('generated')
|
||||
try:
|
||||
generate_documentation('../../telethon_generator/scheme.tl')
|
||||
copy_resources()
|
||||
finally:
|
||||
os.chdir(os.pardir)
|
||||
Reference in New Issue
Block a user