Skip to content

Commit 5a77017

Browse files
committed
Cleaned up names and added instructions
1 parent 31a8765 commit 5a77017

File tree

7 files changed

+185
-56
lines changed

7 files changed

+185
-56
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
1+
output/
2+
samples/
23

34
# Logs
45

README.md

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
In Bloom 5.7, we introduce a modernized, parameterized page layout CSS system, known as "Appearance". This new system conflicts with the arbitrary custom css that was used in prior Bloom versions, mostly in the area of margins.
2+
3+
As of this writing, we are only looking at `customBookStyles.css`, but `customCollectionsStyles.css` have the same problem.
4+
5+
When Bloom encounters a pre-5.7 book with a `customBookStyles.css` that would conflict with the new system, it goes into a kind of "safe mode" that keeps things working by using a legacy `basePage.css`. Better, though, is to migrate the old css to the new system. Conceivably, a reliable program could be written to do this automatically. However at the moment what we do is to write the migrations using the utilities here, and then have a dev evaluate individual migrations and then copy them over to the shipping Bloom. So when Bloom encounters one of these books, we may already have a tested migration for it.
6+
7+
# How to use this system
8+
9+
⚠️ Be careful what you commit to an open source repo. We do not want to expose emails or other private data.
10+
11+
1. As of this writing, bun only works on linux. If you are on windows, just install Windows Subsystem for Linux (not as big of a deal as it sounds), then run in a WSL terminal in VSCODE.
12+
13+
1. Get [bun](https://bun.sh/) installed
14+
15+
1. Install dependencies: `bun install`
16+
17+
1. Download all the `customBookStyles.css` from blorg
18+
19+
`./some-path/BloomBulkDownloader.exe --include "*/*/custom*Styles.css" --syncfolder ./output/downloads --bucket production customBookStyles-files`
20+
21+
1. Process those, grouping them into bins of duplicate stylesheets
22+
23+
`bun run group-stylesheets.ts 13`
24+
25+
Here the number is optional and it limits how many stylesheets will be processed.
26+
27+
1. Process those groups, discarding ones that don't need migration
28+
29+
`bun run filter-stylesheets.ts`
30+
31+
1. Create draft migration files for each one
32+
33+
`bun run create-migrations.ts`
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// This reads in a json of the unique custom sets of rules that need migration.
2+
// For each one, it outputs a folder that serves as a draft "migration" for Bloom.
3+
// See README.md for use.
4+
5+
import fs from "fs";
6+
7+
const data = fs.readFileSync("./output/filter-output.json", "utf8");
8+
9+
// TODO: see https://issues.bloomlibrary.org/youtrack/issue/BL-12857

custom-css-analysis-and-migration/filter-to-problematic-css-rulesets.ts renamed to custom-css-analysis-and-migration/filter-stylesheets.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
// This reads in a json of all unique custom sets of rules, drops the ones that don't need to be migrated,
2+
// and then outputs the results as json file. See README.md for use.
3+
14
import fs from "fs";
25

36
interface Record {
47
content: string;
58
paths: string[];
69
}
710

8-
const data = fs.readFileSync("have-custom.json", "utf8");
11+
const data = fs.readFileSync("./output/group-output.json", "utf8");
912
const records: Record[] = JSON.parse(data);
1013

1114
const count = records.reduce((acc, record) => acc + record.paths.length, 0);
@@ -24,21 +27,37 @@ const recordsWithUniqueifiedPaths = filteredRecords.map((record) => {
2427
const uniquePaths = uniqueFilenames.map((filename) => {
2528
return paths.find((path) => path.endsWith(filename));
2629
});
30+
const instanceId = paths[0].split("/")[2];
2731
return {
2832
book_count: paths.length,
2933
unique_named_books: uniquePaths.length,
34+
first_book: `https://bloomlibrary.org/:search:bookInstanceId%3A${instanceId}`,
3035
css: record.content,
3136
paths,
3237
uniqueified_paths: uniquePaths,
3338
};
3439
});
3540

3641
const sortedRecords = recordsWithUniqueifiedPaths.sort((a, b) => {
37-
return a.uniqueified_paths.length - b.uniqueified_paths.length;
42+
return b.uniqueified_paths.length - a.uniqueified_paths.length;
43+
});
44+
45+
// insert a metadata record into the first position
46+
(sortedRecords as any).unshift({
47+
"total books with custom css rules": count,
48+
"total books with problematic rules": filteredRecords.reduce(
49+
(acc, record) => acc + record.paths.length,
50+
0
51+
),
52+
"total unique css files": records.length,
53+
"unique CSS files with problematic rules": filteredRecords.length,
54+
"counts of unique books for each unique css file": sortedRecords
55+
.map((record) => record.uniqueified_paths.length)
56+
.join(" "),
3857
});
3958

4059
fs.writeFileSync(
41-
"problematic-rules.json",
60+
"./output/filter-output.json",
4261
JSON.stringify(sortedRecords, null, 2)
4362
);
4463

@@ -52,9 +71,9 @@ console.write(
5271
)} books, `
5372
);
5473

55-
console.write(
56-
`${recordsWithUniqueifiedPaths.reduce(
57-
(acc, record) => acc + record.uniqueified_paths.length,
58-
0
59-
)} of which have unique names (should remove most rebrands).\r\n`
60-
);
74+
// console.write(
75+
// `${recordsWithUniqueifiedPaths.reduce(
76+
// (acc, record) => acc + record.uniqueified_paths.length,
77+
// 0
78+
// )} of which have unique names (should remove most rebrands).\r\n`
79+
// );
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// This looks at all the stylesheets, groups them into unique sets of rules,
2+
// and then outputs the results as json file. See README.md for use.
3+
// It takes a single optional argument, which is the number of books to process.
4+
15
import * as fs from "fs";
26
import * as path from "path";
37

@@ -19,7 +23,7 @@ function readFilesRecursively(
1923
const files = fs.readdirSync(dir);
2024
for (const file of files) {
2125
const filePath = path.join(dir, file);
22-
if (count > 100000) return;
26+
if (Bun.argv.length > 2 && count >= Number.parseInt(Bun.argv[2])) return;
2327
if (fs.statSync(filePath).isDirectory()) {
2428
readFilesRecursively(filePath, fileMap);
2529
} else {
@@ -31,30 +35,25 @@ function readFilesRecursively(
3135
if (content === "") continue;
3236

3337
const fileData: FileData = fileMap.get(content) || { content, paths: [] };
34-
fileData.paths.push(
35-
dir.replace("/mnt/c/dev/BloomBulkDownloader/sync", "")
36-
);
38+
fileData.paths.push(dir.replace("./output/downloads", ""));
3739
fileMap.set(content, fileData);
3840
console.log(++count + " " + dir);
3941
}
4042
}
4143
}
4244

43-
function writeUniqueFiles(dir: string, fileMap: Map<string, FileData>): void {
44-
if (!fs.existsSync(dir)) {
45-
fs.mkdirSync(dir);
46-
}
47-
const indexFilePath = path.join(dir, "index.json");
48-
const indexFileContent = JSON.stringify(
49-
Array.from(fileMap.values()),
50-
null,
51-
2
52-
);
53-
fs.writeFileSync(indexFilePath, indexFileContent);
45+
const sourceDir = "./output/downloads";
46+
const fileMap = new Map<string, FileData>();
47+
48+
if (!fs.existsSync("./output")) {
49+
fs.mkdirSync("./output");
5450
}
5551

56-
const sourceDir = "/mnt/c/dev/BloomBulkDownloader/sync";
57-
const targetDir = "/mnt/c/dev/BloomBulkDownloader/unique";
58-
const fileMap = new Map<string, FileData>();
52+
if (fs.existsSync("./output/group-output.json")) {
53+
fs.rmSync("./output/group-output.json");
54+
}
5955
readFilesRecursively(sourceDir, fileMap);
60-
writeUniqueFiles(targetDir, fileMap);
56+
fs.writeFileSync(
57+
"./output/group-output.json",
58+
JSON.stringify(Array.from(fileMap.values()), null, 2)
59+
);

custom-css-analysis-and-migration/migrate-css-files-to-variable-system.ts

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/**
2+
*
3+
* I'm not clear what state this is in, it was an experiment I was working on.
4+
* It probably should become a function that is called by create-migrations.ts
5+
*
6+
* **/
7+
18
import fs from "fs";
29
import { parse, stringify, Rule, Stylesheet, Declaration } from "css";
310

@@ -52,6 +59,21 @@ const sizes = [
5259
{ name: ".Device16x9Landscape", height: 100, width: 177.77777778 },
5360
{ name: ".PictureStoryLandscape", height: 100, width: 177.77777778 },
5461
];
62+
interface PropertyConversions {
63+
[key: string]: string;
64+
}
65+
const propertyConversions: PropertyConversions = {
66+
left: "--page-margin-left",
67+
top: "--page-margin-top",
68+
height: "--page-margin-bottom",
69+
width: "--page-margin-right",
70+
};
71+
const coverPropertyConversions: PropertyConversions = {
72+
left: "--page-margin-left",
73+
top: "--cover-margin-top",
74+
height: "--cover-margin-bottom",
75+
width: "--page-margin-right",
76+
};
5577

5678
const cssFileContent: string = fs.readFileSync("./zebra.css", "utf8");
5779
const cssObject: Stylesheet = parse(cssFileContent);
@@ -94,9 +116,7 @@ if (cssObject.stylesheet && cssObject.stylesheet.rules) {
94116
rule.declarations?.forEach((declaration: Declaration) => {
95117
if (declaration.type === "declaration") {
96118
const key = (declaration as Declaration).property;
97-
interface PropertyConversions {
98-
[key: string]: string;
99-
}
119+
100120
if (size) {
101121
if (key === "width") {
102122
declaration.value =
@@ -113,22 +133,85 @@ if (cssObject.stylesheet && cssObject.stylesheet.rules) {
113133
"mm";
114134
}
115135
}
116-
const propertyConversions: PropertyConversions = {
117-
left: "--page-margin-left",
118-
top: "--page-margin-top",
119-
height: "--page-margin-bottom",
120-
width: "--page-margin-right",
121-
};
122-
123-
if (declaration.property! in propertyConversions) {
124-
declaration.property = propertyConversions[declaration.property!];
136+
137+
const isCover = rule.selectors!.some((sel) => sel.includes("Cover"));
138+
const map = isCover ? coverPropertyConversions : propertyConversions;
139+
if (declaration.property! in map) {
140+
declaration.property = map[declaration.property!];
125141
declaration.value = declaration.value?.replace("!important", "");
126142
}
127143
}
128144
});
145+
146+
// danger, this would probably break if there is anything but classes in the selector
147+
sortClassesInSelector(rule);
148+
149+
// TODO: this doesn't yet move top and bottom margins with .outsideFrontCover and .outsideBackCover to --cover-margin-top and --cover-margin-bottom
150+
151+
sortDeclarations(rule);
129152
}
130153
});
154+
155+
// danger, normally sorting rules is not a good idea!
156+
cssObject.stylesheet.rules = sortRules(cssObject.stylesheet.rules);
131157
}
132158

133159
const modifiedCss: string = stringify(cssObject);
134160
console.log(modifiedCss);
161+
162+
function sortDeclarations(rule: Rule) {
163+
// Define the type of propertyConversions
164+
type PropertyConversions = {
165+
[key: string]: number;
166+
};
167+
168+
// Define the property conversions object
169+
const orderedProperties: PropertyConversions = {
170+
"--page-margin-top": 0,
171+
"--page-margin-bottom": 1,
172+
"--page-margin-left": 2,
173+
"--page-margin-right": 3,
174+
};
175+
176+
// sort the declarations according to the order in propertyConversions
177+
rule.declarations?.sort((a: Declaration, b: Declaration) => {
178+
const aProp = orderedProperties[a.property!];
179+
const bProp = orderedProperties[b.property!];
180+
if (aProp === undefined && bProp === undefined) {
181+
return 0;
182+
} else if (aProp === undefined) {
183+
return 1;
184+
} else if (bProp === undefined) {
185+
return -1;
186+
} else {
187+
return aProp - bProp;
188+
}
189+
});
190+
}
191+
function sortClassesInSelector(rule: any): void {
192+
// sort the classes in the first selector
193+
const classes = rule.selectors[0].trim().split(".").filter(Boolean).sort();
194+
const sortedSelector = "." + classes.join(".");
195+
rule.selectors[0] = sortedSelector;
196+
}
197+
198+
function sortRules(rules: Rule[]): Rule[] {
199+
return rules.sort((a: Rule, b: Rule) => {
200+
if (a.type !== "rule" || b.type !== "rule") return 0;
201+
202+
// sort rules by first selector
203+
const aSelector = a.selectors ? a.selectors[0] : undefined;
204+
const bSelector = b.selectors ? b.selectors[0] : undefined;
205+
if (
206+
aSelector === bSelector ||
207+
aSelector === undefined ||
208+
bSelector === undefined
209+
) {
210+
return 0;
211+
} else if (aSelector > bSelector) {
212+
return 1;
213+
} else {
214+
return -1;
215+
}
216+
});
217+
}

0 commit comments

Comments
 (0)