Added php-generate-tree script
This commit is contained in:
parent
8e94a530b0
commit
28f1407c82
3 changed files with 245 additions and 0 deletions
9
php-generate-tree/example.txt
Normal file
9
php-generate-tree/example.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
├── BOOKSHELF 1: My wonderful shelf of notes
|
||||
│ └── BOOK 39: My lovely book in my notes
|
||||
│ ├── PAGE 2745: A page within the book
|
||||
│ ├── CHAPTER 643: A lone chapter
|
||||
│ └── CHAPTER 644: My chapter with page
|
||||
│ └── PAGE 47830: My new great page
|
||||
├── BOOK 239: Scratch notes
|
||||
│ ├── PAGE 47870: Note A
|
||||
│ └── PAGE 47872: Note B
|
||||
189
php-generate-tree/generate-tree.php
Executable file
189
php-generate-tree/generate-tree.php
Executable file
|
|
@ -0,0 +1,189 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
// API Credentials
|
||||
// You can either provide them as environment variables
|
||||
// or hard-code them in the empty strings below.
|
||||
$baseUrl = getenv('BS_URL') ?: '';
|
||||
$clientId = getenv('BS_TOKEN_ID') ?: '';
|
||||
$clientSecret = getenv('BS_TOKEN_SECRET') ?: '';
|
||||
|
||||
// Script logic
|
||||
////////////////
|
||||
|
||||
// Define the time we wait in between making API requests,
|
||||
// to help keep within rate limits and avoid exhausting resources.
|
||||
$apiPauseMicrosecs = 100;
|
||||
|
||||
// Clean up the base path
|
||||
$baseUrl = rtrim($baseUrl, '/');
|
||||
|
||||
// Get all items from the system keyed by ID
|
||||
$shelvesById = keyById(getAllOfAtListEndpoint("api/shelves", []));
|
||||
$booksById = keyById(getAllOfAtListEndpoint("api/books", []));
|
||||
|
||||
// Fetch books that are on each shelf
|
||||
foreach ($shelvesById as $id => $shelf) {
|
||||
$shelvesById[$id]['books'] = getBooksForShelf($id);
|
||||
usleep($apiPauseMicrosecs);
|
||||
}
|
||||
|
||||
// For each book, fetch its contents list
|
||||
foreach ($booksById as $id => $book) {
|
||||
$booksById[$id]['contents'] = apiGetJson("api/books/{$id}")['contents'] ?? [];
|
||||
usleep($apiPauseMicrosecs);
|
||||
}
|
||||
|
||||
// Cycle through the shelves and display their contents
|
||||
$isBookShownById = [];
|
||||
foreach ($shelvesById as $id => $shelf) {
|
||||
output($shelf, 'bookshelf', [false]);
|
||||
$bookCount = count($shelf['books']);
|
||||
for ($i=0; $i < $bookCount; $i++) {
|
||||
$bookId = $shelf['books'][$i];
|
||||
$book = $booksById[$bookId] ?? null;
|
||||
if ($book) {
|
||||
outputBookAndContents($book, [false, $i === $bookCount - 1]);
|
||||
$isBookShownById[strval($book['id'])] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cycle through books and display any that have not been
|
||||
// part of a shelve's output
|
||||
foreach ($booksById as $id => $book) {
|
||||
if (isset($isBookShownById[$id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outputBookAndContents($book, [false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a book for display, along with its contents.
|
||||
*/
|
||||
function outputBookAndContents(array $book, array $depthPath): void
|
||||
{
|
||||
output($book, 'book', $depthPath);
|
||||
$childCount = count($book['contents']);
|
||||
for ($i=0; $i < $childCount; $i++) {
|
||||
$child = $book['contents'][$i];
|
||||
$childPath = array_merge($depthPath, [($i === $childCount - 1)]);
|
||||
output($child, $child['type'], $childPath);
|
||||
$pages = $child['pages'] ?? [];
|
||||
$pageCount = count($pages);
|
||||
for ($j=0; $j < count($pages); $j++) {
|
||||
$page = $pages[$j];
|
||||
$innerPath = array_merge($childPath, [($j === $pageCount - 1)]);
|
||||
output($page, 'page', $innerPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a single item for display.
|
||||
*/
|
||||
function output(array $item, string $type, array $depthPath): void
|
||||
{
|
||||
$upperType = strtoupper($type);
|
||||
$prefix = '';
|
||||
$depth = count($depthPath);
|
||||
for ($i=0; $i < $depth; $i++) {
|
||||
$isLastAtDepth = $depthPath[$i];
|
||||
$end = ($i === $depth - 1);
|
||||
if ($end) {
|
||||
$prefix .= $isLastAtDepth ? '└' : '├';
|
||||
} else {
|
||||
$prefix .= $isLastAtDepth ? ' ' : '│ ';
|
||||
}
|
||||
}
|
||||
echo $prefix . "── {$upperType} {$item['id']}: {$item['name']}\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Key an array of array-based data objects by 'id' value.
|
||||
*/
|
||||
function keyById(array $data): array
|
||||
{
|
||||
$byId = [];
|
||||
foreach ($data as $item) {
|
||||
$id = $item['id'];
|
||||
$byId[$id] = $item;
|
||||
}
|
||||
return $byId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the books for the given shelf ID.
|
||||
* Returns an array of the book IDs.
|
||||
*/
|
||||
function getBooksForShelf(int $shelfId): array
|
||||
{
|
||||
$resp = apiGetJson("api/shelves/{$shelfId}");
|
||||
return array_map(function ($bookData) {
|
||||
return $bookData['id'];
|
||||
}, $resp['books'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume all items from the given API listing endpoint.
|
||||
*/
|
||||
function getAllOfAtListEndpoint(string $endpoint, array $params): array
|
||||
{
|
||||
global $apiPauseMicrosecs;
|
||||
$count = 100;
|
||||
$offset = 0;
|
||||
$all = [];
|
||||
|
||||
do {
|
||||
$endpoint = $endpoint . '?' . http_build_query(array_merge($params, ['count' => $count, 'offset' => $offset]));
|
||||
$resp = apiGetJson($endpoint);
|
||||
|
||||
$total = $resp['total'] ?? 0;
|
||||
$new = $resp['data'] ?? [];
|
||||
array_push($all, ...$new);
|
||||
$offset += $count;
|
||||
usleep($apiPauseMicrosecs);
|
||||
} while ($offset < $total);
|
||||
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a simple GET HTTP request to the API.
|
||||
*/
|
||||
function apiGet(string $endpoint): string
|
||||
{
|
||||
global $baseUrl, $clientId, $clientSecret;
|
||||
$url = rtrim($baseUrl, '/') . '/' . ltrim($endpoint, '/');
|
||||
$opts = ['http' => ['header' => "Authorization: Token {$clientId}:{$clientSecret}"]];
|
||||
$context = stream_context_create($opts);
|
||||
return @file_get_contents($url, false, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a simple GET HTTP request to the API &
|
||||
* decode the JSON response to an array.
|
||||
*/
|
||||
function apiGetJson(string $endpoint): array
|
||||
{
|
||||
$data = apiGet($endpoint);
|
||||
$array = json_decode($data, true);
|
||||
|
||||
if (!is_array($array)) {
|
||||
dd("Failed request to {$endpoint}", $data);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* DEBUG: Dump out the given variables and exit.
|
||||
*/
|
||||
function dd(...$args)
|
||||
{
|
||||
foreach ($args as $arg) {
|
||||
var_dump($arg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
47
php-generate-tree/readme.md
Normal file
47
php-generate-tree/readme.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Generate Tree
|
||||
|
||||
This script will scan through all pages, chapters books and shelves via the API to generate a big tree structure list in plaintext.
|
||||
|
||||
**This is a very simplistic single-script-file example of using the endpoints API together**
|
||||
, it is not a fully-featured & validated script, it error handling is very limited.
|
||||
|
||||
Keep in mind, The tree generated will reflect content visible to the API user used when running the script.
|
||||
|
||||
This script follows a `((Shelves > Books > (Chapters > Pages | Pages)) | Books)` structure so books and their contents may be repeated if on multiple shelves. Books not on any shelves will be shown at the end.
|
||||
|
||||
## Requirements
|
||||
|
||||
You will need php (~8.1+) installed on the machine you want to run this script on.
|
||||
You will also need BookStack API credentials (TOKEN_ID & TOKEN_SECRET) at the ready.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
# Downloading the script
|
||||
# ALTERNATIVELY: Clone the project from GitHub and run locally.
|
||||
curl https://raw.githubusercontent.com/BookStackApp/api-scripts/main/php-generate-tree/generate-tree.php > generate-tree.php
|
||||
|
||||
# Setup
|
||||
# ALTERNATIVELY: Open the script and edit the variables at the top.
|
||||
export BS_URL=https://bookstack.example.com # Set to be your BookStack base URL
|
||||
export BS_TOKEN_ID=abc123 # Set to be your API token_id
|
||||
export BS_TOKEN_SECRET=123abc # Set to be your API token_secret
|
||||
|
||||
# Running the script
|
||||
php generate-tree.php
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Generate out the tree to the command line
|
||||
php generate-tree.php
|
||||
|
||||
# Generate & redirect output to a file
|
||||
php generate-tree.php > bookstack-tree.txt
|
||||
|
||||
# Generate with the output shown on the command line and write to a file
|
||||
php generate-tree.php | tee bookstack-tree.txt
|
||||
```
|
||||
|
||||
An example of the output can be seen in the [example.txt](./example.txt) file within the directory of this readme.
|
||||
Loading…
Add table
Add a link
Reference in a new issue