forked from livecode/livecode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrelease_notes_builder.livecodescript
More file actions
1529 lines (1227 loc) · 46.9 KB
/
release_notes_builder.livecodescript
File metadata and controls
1529 lines (1227 loc) · 46.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
script "ReleaseNotesBuilder"
local sMarkdownText
local sVersion
local sOutputPath
private command Initialize pVersion, pOutputPath
if pOutputPath is empty then
throw "Release notes output path must be specified"
exit Initialize
end if
start using stack (builderSystemFolder() & slash & "markdown_compiler.livecodescript")
set the defaultfolder to builderRepoFolder()
set the hideconsolewindows to true
put empty into sMarkdownText
put pVersion into sVersion
put pOutputPath into sOutputPath
end Initialize
private command Finalize
stop using stack (builderSystemFolder() & slash & "markdown_compiler.livecodescript")
end Finalize
command releaseNotesBuilderRun pEdition, pVersion, pReleaseType, pOutputDir
local tError
builderLog "report", "Building release notes for version" && pVersion
try
Initialize pVersion, pOutputDir
BaseCreate
V1NotesCreate "engine", "engine", "Engine changes"
V1NotesCreate "ide", "IDE", "IDE changes"
V2NotesCreate "lcb", "LCB", "LiveCode Builder changes"
ExtensionsCreate
DictionaryCreate
PreviousCreate
OutputNotes
OutputUpdates
Finalize
return FileGetUTF8Contents(OutputGetUpdatesFilename("html"))
catch tError
builderLog "error", tError
end try
end releaseNotesBuilderRun
private function NotesGetExperimentalText
return "<div class=" & quote & "experimental" & quote & ">" & \
"Important: This feature is currently experimental. This means it may not be complete, or may fail in some circumstances that you would expect it to work. Please do not be afraid to try it out as we need feedback to develop it further." & \
"</div>"
end NotesGetExperimentalText
----------------------------------------------------------------
-- Output
----------------------------------------------------------------
private function OutputGetNotesFilename tSuffix, pVersion
if pVersion is empty then
put sVersion into pVersion
end if
local tBasename
put "LiveCodeNotes-" & replaceText(pVersion, "[-,\.]", "_") into tBasename
return sOutputPath & slash & tBasename & "." & tSuffix
end OutputGetNotesFilename
private function OutputGetUpdatesFilename tSuffix, pVersion
if pVersion is empty then
put sVersion into pVersion
end if
local tBasename
put "LiveCodeUpdates-" & replaceText(pVersion, "[-,\.]", "_") into tBasename
return sOutputPath & slash & tBasename & "." & tSuffix
end OutputGetUpdatesFilename
private function OutputGetGuideFilename
builderEnsureFolder builderBuiltGuidesFolder()
return builderBuiltGuidesFolder() & slash & "Release Notes.md"
end OutputGetGuideFilename
private function OutputGetNotesUrl tSuffix, pVersion
put replaceText(pVersion, "[-,\.]", "_") into pVersion
return "https://downloads.livecode.com/livecode/" & pVersion & \
"/LiveCodeNotes-" & pVersion & "." & tSuffix
end OutputGetNotesUrl
private function OutputGetNotesTitle
return merge("LiveCode [[sVersion]] Release Notes")
end OutputGetNotesTitle
private command OutputNotes
OutputNotesMarkdown
OutputNotesHtml
OutputNotesPdf
OutputNotesGuide
end OutputNotes
private command OutputNotesMarkdown
local tPath
put OutputGetNotesFilename("md") into tPath
builderLog "report", merge("Writing [[tPath]]")
FileSetUTF8Contents tPath, sMarkdownText["notes"]
end OutputNotesMarkdown
private command OutputNotesGuide
local tPath
put OutputGetGuideFilename() into tPath
builderLog "report", merge("Writing [[tPath]]")
FileSetUTF8Contents tPath, sMarkdownText["notes"]
end OutputNotesGuide
private command OutputNotesHtml
-- Try to do the minimum possible escaping to be able to insert the
-- markdown into a JavaScript string
local tEscaped
put sMarkdownText["notes"] into tEscaped
replace "\" with "\\" in tEscaped
replace "'" with "\'" in tEscaped
replace return with "\n" in tEscaped
local tHtmlTemplatePath
put builderSystemFolder() & "/release-notes-template.html" into tHtmlTemplatePath
local tHtml
put FileGetUTF8Contents(tHtmlTemplatePath) into tHtml
replace "@MARKDOWN@" with tEscaped in tHtml
local tOutpath
put OutputGetNotesFilename("html") into tOutpath
builderLog "report", merge("Writing [[tOutpath]]")
FileSetUTF8Contents tOutpath, tHtml
end OutputNotesHtml
private command OutputNotesPdf
local tCommand
-- Use wkhtmltopdf to convert the HTML representation
if $WKHTMLTOPDF is not empty then
put $WKHTMLTOPDF into tCommand
else
put builderRepoFolder() & "/builder/wkhtmltopdf" into tCommand
end if
local tTitle, tDate, tHtmlPath, tPdfPath
put OutputGetNotesTitle() into tTitle
put the date into tDate
put OutputGetNotesFilename("html") into tHtmlPath
put OutputGetNotesFilename("pdf") into tPdfPath
local tArgs
put empty into tArgs
put merge("--header-right '[[tTitle]] [[tDate]]' ") after tArgs
put "--header-font-size 8 --header-spacing 5 " after tArgs
put "--footer-center [page] --footer-font-size 8 --footer-spacing 5 " after tArgs
put "--margin-top 30 --margin-bottom 20 --margin-left 20 --margin-right 20 " after tArgs
put "--enable-internal-links --encoding UTF-8 " after tArgs
local tExitCode
builderLog "report", merge("Generating [[tPdfPath]] with [[tCommand]]")
get shell(tCommand && tArgs && tHtmlPath && tPdfPath)
put the result into tExitCode
if tExitCode is not 0 then
throw merge("Failed to run [[tCommand]]: exit code [[tExitCode]]")
end if
end OutputNotesPdf
private command OutputUpdates
OutputUpdatesMarkdown
OutputUpdatesHtml
end OutputUpdates
private command OutputUpdatesMarkdown
local tPath
put OutputGetUpdatesFilename("md") into tPath
builderLog "report", merge("Writing [[tPath]]")
FileSetUTF8Contents tPath, sMarkdownText["updates"]
end OutputUpdatesMarkdown
private command OutputUpdatesHtml
local tHtml, tToc
put markdownToHtml(sMarkdownText["updates"], 3, 0, tToc, true) into tHtml
local tPath
put OutputGetUpdatesFilename("html") into tPath
builderLog "report", merge("Writing [[tPath]]")
FileSetUTF8Contents tPath, tHtml
end OutputUpdatesHtml
----------------------------------------------------------------
-- Base file sections
----------------------------------------------------------------
private command BaseCreate
builderLog "report", "Creating base release notes"
BaseCreateTitle
BaseCreateContents
BaseCreateOverview
BaseCreateIssues
MarkdownAppend "notes", BaseReadFile("platforms")
MarkdownAppend "notes", BaseReadFile("setup")
MarkdownAppend "notes", BaseReadFile("proposed_changes")
BaseCreateUpdates
end BaseCreate
private command BaseCreateTitle
MarkdownAppend "notes", "<h1 class=" & quote & "title" & quote & ">" & OutputGetNotesTitle() & "</h1>" & return
end BaseCreateTitle
private command BaseCreateContents
MarkdownAppend "notes", "[TOC]"
end BaseCreateContents
private command BaseCreateOverview
local tOverview
put BaseReadFile("overview") into tOverview
if tOverview is empty then
put "# Overview" & return & return after tOverview
put merge("This document describes all the changes that have been made for LiveCode [[sVersion]], including bug fixes and new syntax.") after tOverview
end if
MarkdownAppend "notes", tOverview
end BaseCreateOverview
private command BaseCreateIssues
local tIssues
put BaseReadFile("issues") into tIssues
if tIssues is empty then
put "# Known Issues" & return & return after tIssues
put "There are no known issues with this release." after tIssues
end if
MarkdownAppend "notes", tIssues
end BaseCreateIssues
private function BaseReadFile pBasename
local tPath
put FileGetPath() & slash & pBasename & ".md" into tPath
if there is a file tPath then
return FileGetUTF8Contents(tPath)
else
return empty
end if
end BaseReadFile
private command BaseCreateUpdates
MarkdownAppend "updates", merge("LiveCode [[sVersion]] is now available.") & return
MarkdownAppend "updates", merge("The full [[sVersion]] release notes are available on the LiveCode website.") & return
MarkdownAppend "updates", "Changes in this release include:" & return
end BaseCreateUpdates
----------------------------------------------------------------
-- Old-style release notes
----------------------------------------------------------------
/*
The IDE and the engine use "old-style" release notes. These are
markdown fragments that have "feature" or "bugfix" in their
filenames.
Each file details a single bug fix or feature implementation.
* Name the files "bugfix-<bug number>.md",
"bugfix-<bug number>-suffix.md", or "feature-<description>.md"
* Put the title as the first line, e.g. "# <title>"
Single-line files are added to the table of bugfixes. Multi-line
files get their own section in the release notes.
*/
private command V1NotesCreate pType, pReadableType, pTitle
builderLog "report", merge("Creating [[pReadableType]] release notes")
-- First, gather all files, their contents, and their metadata
local tScannedNotes
put V1NotesScan(pType) into tScannedNotes
-- Next generate markdown
if V1NotesHaveContent(tScannedNotes) then
MarkdownAppend "notes", merge("# [[pTitle]]")
MarkdownAppend "updates", merge("# [[pTitle]]")
V1NotesGenerate pType, pReadableType, tScannedNotes
end if
end V1NotesCreate
/*
Build up a datastructure containing all the (raw) data gathered
from the target directory. The return value is a nested array:
{
"<version>": {
"<filename>": {
"basename": "<filename without suffix or path>",
"markdown": "<file content>",
"metadata": { <metadata key-value pairs> }
}
}
}
*/
private function V1NotesScan pType
local tTags, tNumTags
put GitGetRelevantTags(pType) into tTags
put the number of lines in tTags into tNumTags
local tResult
put empty into tResult
local tVersion, tBaseVersion, tPrevVersion, tTagOffset
local tAllFiles, tVersionFiles, tFile
put line 1 of tTags into tBaseVersion
put tBaseVersion into tVersion
-- Get a list of all files to be considered
put GitGetChangedFiles(pType, tBaseVersion, line -1 of tTags) into tAllFiles
put 2 into tTagOffset
repeat while tTagOffset <= tNumTags
put tVersion into tPrevVersion
put line tTagOffset of tTags into tVersion
-- Figure out which files should be considered for this version
put empty into tVersionFiles
repeat for each line tFile in GitGetChangedFiles(pType, tPrevVersion, tVersion)
if tFile is among the lines of tAllFiles then
put tFile & return after tVersionFiles
end if
end repeat
V1NotesScanVersion pType, tVersion, tVersionFiles, tResult
add 1 to tTagOffset
end repeat
return tResult
end V1NotesScan
private command V1NotesScanVersion pType, pVersion, pFiles, @xScan
local tFile, tOldFile, tContents, tError, tFileInfo, tVersion, tBasename
repeat for each line tFile in pFiles
-- If this file was already processed once before, delete its
-- record (we'll generate a new one)
repeat for each key tVersion in xScan
if tFile is among the keys of xScan[tVersion] then
delete variable xScan[tVersion][tFile]
end if
end repeat
try
put FileGetUTF8Contents(tFile) into tContents
catch tError
builderLog "report", tERror
next repeat
end try
-- Extract metadata & markdown sections
MarkdownSplitMetadata tContents, tFileInfo["markdown"], tFileInfo["metadata"]
-- Allow automatically-generated information to be overwritten by metadata
put tFileInfo["metadata"]["version"] into tVersion
if tVersion is empty then
put pVersion into tVersion
end if
put tFileInfo["metadata"]["basename"] into tBasename
if tBasename is empty then
put FileGetBasename(tFile) into tBasename
end if
put tBasename into tFileInfo["basename"]
put tFileInfo into xScan[tVersion][tFile]
end repeat
end V1NotesScanVersion
private function V1NotesHaveContent pScanData
local tTagData
repeat for each element tTagData in pScanData
if V1NotesHaveContentVersion(tTagData) then
return true
end if
end repeat
return false
end V1NotesHaveContent
private function V1NotesHaveContentVersion pScanData
local tFileInfo
repeat for each element tFileInfo in pScanData
if tFileInfo["basename"] contains "feature" then
return true
end if
if tFileInfo["basename"] contains "bugfix" then
return true
end if
end repeat
return false
end V1NotesHaveContentVersion
private command V1NotesGenerate pType, pReadableType, pScanData
local tBugInfo
-- Process versions in reverse order
local tTags, tTagCount
put the keys of pScanData into tTags
GitSortTags tTags
put the number of lines in tTags into tTagCount
local tTag
repeat tTagCount times
put line tTagCount of tTags into tTag
v1NotesGenerateVersion pType, tTag, pScanData[tTag], tBugInfo
subtract 1 from tTagCount
end repeat
BugGenerate tBugInfo, pReadableType
end V1NotesGenerate
private command V1NotesGenerateVersion pType, pVersion, pScanData, @xBugInfo
local tFile, tFeatures, tBugfixes, tNote
set the itemdelimiter to slash
-- Split note files into features vs bugfixes
local tFileInfo
repeat for each key tFile in pScanData
put pScanData[tFile] into tFileInfo
if tFileInfo["basename"] contains "feature" then
put tFileInfo["basename"] & slash & tFile & return after tFeatures
else if tFileInfo["basename"] contains "bugfix" then
put tFileInfo["basename"] & slash & tFile & return after tBugfixes
end if
end repeat
-- Process features first
sort lines of tFeatures by item 1 of each
repeat for each line tNote in tFeatures
put item 2 to -1 of tNote into tFile
V1NotesGenerateFeature pType, pVersion, pScanData[tFile]
end repeat
-- Now process bugfixes
sort lines of tBugfixes by item 1 of each
repeat for each line tNote in tBugfixes
put item 2 to -1 of tNote into tFile
V1NotesGenerateBugfix pType, pVersion, pScanData[tFile], xBugInfo
end repeat
end V1NotesGenerateVersion
private function V1NotesGetBugId pFileInfo
local tBasename, tBugId
put pFileInfo["basename"] into tBasename
-- Try strict mode first, and issue a warning if no match
get matchText(tBasename, "(?i)^bugfix-(\d*)($|-)", tBugId)
if tBugId is not empty then
return tBugId
end if
-- Now try again, more tolerantly
builderLog "warning", merge("Probably misnamed bug fix note '[[tBasename]]'")
get matchText(pFileInfo["basename"], "(?i)bugfix-(\d*)", tBugId)
if tBugId is empty then
throw "Could not determine bug ID for '[[tBasename]]'"
end if
return tBugId
end V1NotesGetBugId
private command V1NotesGenerateBugfix pType, pVersion, pFileInfo, @xBugInfo
local tBugId, tHeader, tBody
put V1NotesGetBugId(pFileInfo) into tBugId
V1NotesSplitHeader pType, pFileInfo, tHeader, tBody
if tHeader is empty then
exit V1NotesGenerateBugfix
end if
BugAddInfo xBugInfo, pVersion, tBugId, tHeader
-- If the body is non-empty, generate a paragraph in the
-- release notes for this bugfix
if tBody is not empty then
V1NotesGenerateBlock pVersion, tHeader, tBody
end if
end V1NotesGenerateBugfix
private command V1NotesGenerateFeature pType, pVersion, pFileInfo
local tBasename, tHeader, tBody
put pFileInfo["basename"] into tBasename
V1NotesSplitHeader pType, pFileInfo, tHeader, tBody
if tHeader is empty then
exit V1NotesGenerateFeature
end if
if tBody is empty then
builderLog "warning", merge("Empty [[pType]] feature note '[[tBaseName]]'")
exit V1NotesGenerateFeature
end if
V1NotesGenerateBlock pVersion, tHeader, tBody
end V1NotesGenerateFeature
private command V1NotesSplitHeader pType, pFileInfo, @rHeader, @rBody
local tBody, tBasename
put pFileInfo["markdown"] into tBody
put pFileInfo["basename"] into tBasename
-- Delete leading empty lines
repeat while word 1 to -1 of (line 1 of tBody) is empty
if tBody is empty then
builderLog "warning", merge("Empty [[pType]] bugfix note '[[tBasename]]'")
put empty into rHeader
put empty into rBody
exit V1NotesSplitHeader
end if
delete line 1 of tBody
end repeat
local tFirstLine
put word 1 to -1 of (line 1 of tBody) into tFirstLine
if tFirstLine begins with "#" then
put char 2 to -1 of tFirstLine into rHeader
put word 1 to -1 of (line 2 to -1 of tBody) into rBody
end if
if rHeader is empty then
builderLog "warning", merge("Bad [[pType]] bugfix header in [[tBasename]]")
exit V1NotesSplitHeader
end if
end V1NotesSplitHeader
private command V1NotesGenerateBlock pVersion, pHeader, pBody
if pVersion is "HEAD" then
put sVersion into pVersion
end if
-- Handle "experimental" annotation
local tAnnotation, tExperimental
if the last word of pHeader is "(experimental)" then
put word 1 to -2 of pHeader into pHeader
put " - experimental" into tAnnotation
put true into tExperimental
else
put false into tExperimental
end if
MarkdownAppend "notes", merge("## [[pHeader]] ([[pVersion]][[tAnnotation]])")
MarkdownAppend "notes", pBody & return
if tExperimental then
MarkdownAppend "notes", NotesGetExperimentalText()
end if
-- Include only brand new notes in the updater notes
if pVersion is sVersion then
MarkdownAppend "updates", merge("## [[pHeader]]")
MarkdownAppend "updates", pBody & return
if tExperimental then
MarkdownAppend "updates", NotesGetExperimentalText()
end if
end if
end V1NotesGenerateBlock
----------------------------------------------------------------
-- New-style notes
----------------------------------------------------------------
private command V2NotesCreate pType, pReadableType, pTitle
builderLog "report", merge("Creating [[pReadableType]] release notes")
local tScannedNotes
put V2NotesScan(pType) into tScannedNotes
local tCollated, tBugInfo
V2NotesCollate pType, tScannedNotes, tCollated, tBugInfo
if V2NotesHaveContent(tCollated, tBugInfo) then
MarkdownAppend "notes", merge("# [[pTitle]]")
MarkdownAppend "updates", merge("# [[pTitle]]")
V2NotesGenerate pType, tCollated, 1
BugGenerate tBugInfo, pReadableType
end if
end V2NotesCreate
-- There isn't actually any difference between the information
-- that needs to be scanned for V2 notes and the information that
-- needs to be scanned for V1 notes.
private function V2NotesScan pType
return V1NotesScan(pType)
end V2NotesScan
/*
Create a tree of section information from the results of scanning
the release notes:
{
"__count": <number of child sections>,
"__markdown": "<text>",
"__name": "<title of section>",
1: {
"__count": ...,
"__markdown" ...,
"__name", ...,
...
},
2: {
...
},
}
*/
private command V2NotesCollate pType, pScanInfo, @rCollated, @rBugInfo
local tTags, tFile, tTagCount, tVersion
-- Collate notes in descending order of version, so newest appear at the top
put the keys of pScanInfo into tTags
GitSortTags tTags
put the number of elements in pScanInfo into tTagCount
repeat tTagCount times
put line tTagCount of tTags into tVersion
repeat for each key tFile in pScanInfo[tVersion]
V2NotesCollateFile pType, tVersion, pScanInfo[tVersion][tFile], rCollated, rBugInfo
end repeat
subtract 1 from tTagCount
end repeat
end V2NotesCollate
private command V2NotesCollateFile pType, pVersion, pFileInfo, @xCollated, @xBugInfo
local tLine, tSectionPath, tBasename
put pFileInfo["basename"] into tBasename
local tBugId, tBugDesc, tBugPath
local tLevel, tName, tOldLevel, tHaveContent
put 0 into tLevel
put 0 into tOldLevel
put false into tHaveContent
repeat for each line tLine in pFileInfo["markdown"]
-- First check if this is a bug info line
V2NotesExtractBugInfo tLine, tBugId, tBugDesc
if tBugId is not empty then
BugAddInfo xBugInfo, pVersion, tBugId, tBugDesc
next repeat
end if
-- Second, check if it's a section control line
put tLevel into tOldLevel
V2NotesExtractSectionInfo tLine, tLevel, tName
if tLevel is not empty then
if tName is empty then
builderLog "warning", merge("Invalid section name in [[pType]] note [[tBasename]]")
next repeat
end if
-- If changing to a section that's the same level or
-- higher than the current section but without any
-- intervening content, generate a warning
if (not tHaveContent) and tLevel <= tOldLevel then
builderLog "warning", merge("Section without content in [[pType]] note '[[tBasename]]'")
end if
V2NotesUpdateSectionPath tLevel, tName, xCollated, tSectionPath
put false into tHaveContent
next repeat
end if
-- Otherwise, just add it to the current section of the
-- collated notes
put tLine & return after xCollated[tSectionPath]["__markdown"]
put pVersion into xCollated[tSectionPath]["__version"]
if word 1 to -1 of tLine is not empty then
put true into tHaveContent
end if
end repeat
end V2NotesCollateFile
private command V2NotesUpdateSectionPath pLevel, pName, @xCollated, @xSectionPath
if pLevel is 0 then
exit V2NotesUpdateSectionPath
end if
if not xSectionPath is an array then
put 1 into xSectionPath[1]
delete variable xSectionPath[1]
end if
-- Truncate the path to the level before the current one
repeat while the number of elements in xSectionPath >= pLevel
delete variable xSectionPath[the number of elements in xSectionPath]
end repeat
-- Find out how many subsections there already are at this level.
-- It's necessary to do a few contortions to cope with the case that
-- the truncated path is empty.
local tPath, tCount
put xSectionPath into tPath
put "__count" into tPath[pLevel]
put xCollated[tPath] into tCount
-- Check to see if there is already a subsection with the specified name
local tId
repeat with tId = 1 to tCount
put tId into tPath[pLevel]
if xCollated[tPath]["__name"] is pName then
put tId into xSectionPath[pLevel]
exit V2NotesUpdateSectionPath
end if
end repeat
-- No existing section matches, so add a new one. Do the "cope with
-- empty array" dance again.
put "__count" into tPath[pLevel]
add 1 to xCollated[tPath]
put xCollated[tPath] into tId
-- Initialise the new section record
put tId into xSectionPath[pLevel]
put pName into xCollated[xSectionPath]["__name"]
put 0 into xCollated[xSectionPath]["__count"]
end V2NotesUpdateSectionPath
private command V2NotesExtractBugInfo pLine, @rId, @rDesc
get matchText(pLine, "^\s*#\s+\[(\w*)\]\s+(.*)$", rId, rDesc)
end V2NotesExtractBugInfo
private command V2NotesExtractSectionInfo pLine, @rLevel, @rName
local tPrefix
get matchText(pLine, "^\s*(#*)\s+(.*)$", tPrefix, rName)
if tPrefix is not empty then
put the number of chars in tPrefix into rLevel
else
put empty into rLevel
end if
end V2NotesExtractSectionInfo
-- Append collated section data from <pCollatedSection> as a new top-level
-- section in <xCollated> with <pName>. If there is already a section
-- named <pName> in <xCollated>, generate an error.
private command V2NotesAppendSection pName, pCollatedSection, @xCollated
local tId
repeat with tId = 1 to xCollated["__count"]
if xCollated[tId]["__name"] is pName then
throw merge("Notes data already contains a section '[[pName]]'")
end if
end repeat
add 1 to xCollated["__count"]
put xCollated["__count"] into tId
put pCollatedSection into xCollated[tId]
put pName into xCollated[tId]["__name"]
end V2NotesAppendSection
private function V2NotesHaveContent pCollated, pBugInfo
if (word 1 to -1 of pCollated["__markdown"]) is not empty then
return true
end if
-- Recurse into child nodes
local tId
repeat with tId = 1 to pCollated["__count"]
if V2NotesHaveContent(pCollated[tId]) then
return true
end if
end repeat
if the number of elements in pBugInfo > 0 then
return true
end if
return false
end V2NotesHaveContent
private command V2NotesGenerate pType, pCollated, pLevel
MarkdownAppend "notes", V2NotesGenerateContent(pType, pCollated, pLevel)
MarkdownAppend "updates", V2NotesGenerateContent(pType, pCollated, pLevel)
end V2NotesGenerate
-- If <pOnlyCurrent> is true, then only generate content that's brand new in
-- the release that notes are being generated for
private function V2NotesGenerateContent pType, pCollated, pLevel
-- Compute the content from this node + all child nodes
local tContent
if (word 1 to -1 of pCollated["__markdown"]) is not empty then
put pCollated["__markdown"] into tContent
if the last char of tContent is not return then
put return after tContent
end if
end if
-- Recurse into all child nodes
local tSectionContent, tHeader
repeat with tId = 1 to pCollated["__count"]
put V2NotesGenerateContent(pType, pCollated[tId], pLevel + 1) into tSectionContent
-- Only generate a subsection header if the subsection (and all
-- the subsections it contains) doesn't contain any notes
if tSectionContent is not empty then
put "#" into tHeader
repeat pLevel times
put "#" after tHeader
end repeat
put " " & pCollated[tId]["__name"] & return after tHeader
put return & tHeader after tContent
put tSectionContent after tContent
end if
end repeat
return tContent
end V2NotesGenerateContent
----------------------------------------------------------------
-- Extension release notes
----------------------------------------------------------------
constant kExtensionTypes = "widgets,libraries,modules"
constant kExtensionStrings = "widget,library,module"
private command ExtensionsCreate
BuilderLog "report", "Creating extension release notes"
MarkdownAppend "notes", "# LiveCode extension changes"
MarkdownAppend "updates", "# LiveCode extension changes"
local tType, tTypePath, tFolder
local tCollated, tBugInfo
repeat for each item tType in kExtensionTypes
put FileGetPath("extensions") & slash & tType into tTypePath
repeat for each line tFolder in FileGetSubFolders(tTypePath)
ExtensionsCreatePath tFolder, tCollated, tBugInfo
end repeat
end repeat
V2NotesGenerate "extensions", tCollated, 1
BugGenerate tBugInfo, "extension"
end ExtensionsCreate
private command ExtensionsCreatePath pExtPath, @xCollated, @xBugInfo
set the itemdelimiter to slash
-- Skip the extension if it doesn't appear to have been compiled
if there is not a file ExtensionsGetManifestPath(pExtPath) then
builderLog "report", "Skipping uncompiled extension" && item -1 of pExtPath
exit ExtensionsCreatePath
end if
builderLog "report", "Creating release notes for" && item -1 of pExtPath
-- Scan and collate all information for this particular extension
local tNotesPath
put pExtPath & "/notes" into tNotesPath
if there is not a folder tNotesPath then
exit ExtensionsCreatePath
end if
local tScan, tExtCollated
put V2NotesScan(tNotesPath) into tScan
V2NotesCollate pExtPath, tScan, tExtCollated, xBugInfo
-- Add the collated info in the top-level collation structure
try
local tName
put ExtensionsGetSectionName(pExtPath) into tName
catch tError
builderLog "warning", tError
exit ExtensionsCreatePath
end try
V2NotesAppendSection tName, tExtCollated, xCollated
end ExtensionsCreatePath
private function ExtensionsGetSectionName pExtPath
local tName, tType, tTypeOffset, tPrettyType
set the itemdelimiter to slash
put item -2 of pExtPath into tType
set the itemdelimiter to comma
put itemoffset(tType, kExtensionTypes) into tTypeOffset
put item tTypeOffset of kExtensionStrings into tPrettyType
put ExtensionsGetName(pExtPath) into tName
if word -1 of tName is not tPrettyType then
put space & tPrettyType after tName
end if
return tName
end ExtensionsGetSectionName
private function ExtensionsGetName pExtPath
local tManifest, tXmlId
put FileGetContents(ExtensionsGetManifestPath(pExtPath)) into tManifest
put revXMLCreateTree(tManifest, true, true, false) into tXmlId
if tXmlId begins with "xmlerr" then
throw "Invalid extension manifest XML in [[tManifestFile]]"
end if
local tTargetName
put textDecode(revXMLNodeContents(tXmlId, "/package/title"), "UTF-8") into tTargetName
if tTargetName begins with "xmlerr" then
return empty
end if
return tTargetName
end ExtensionsGetName
private function ExtensionsGetKind pExtPath
-- Horrible-ish hack for extracting the "real" name of the LiveCode
-- module. See also tools/build-extensions.sh.
local tShortName
set the itemdelimiter to slash
put item -1 of pExtPath into tShortName
local tLcbSource, tLine, tModuleName
put FileGetUTF8Contents(merge("[[pExtPath]]/[[tShortName]].lcb")) into tLcbSource
repeat for each line tLine in tLcbSource
get matchText(tLine, "(?i)^\s*(?:module|widget|library)\s+([\w.]*)", tModuleName)
if tModuleName is not empty then
exit repeat
end if
end repeat
return tModuleName
end ExtensionsGetKind
private function ExtensionsGetManifestPath pExtPath
local tManifestFile
put FileGetPath("built-extensions") into tManifestFile
put slash & ExtensionsGetKind(pExtPath) after tManifestFile
put slash & "manifest.xml" after tManifestFile
return tManifestFile
end ExtensionsGetManifestPath
----------------------------------------------------------------
-- Dictionary change generation
----------------------------------------------------------------
private command DictionaryCreate
builderLog "report", "Creating dictionary release notes"
local tAdded, tModified
DictionaryScan tAdded, tModified
sort lines of tAdded ascending text
sort lines of tModified ascending text
if tAdded is not empty then
MarkdownAppend "notes", "# Dictionary additions"
MarkdownAppend "notes", tAdded
end if
--MarkdownAppend "notes", "# Dictionary changes"
--MarkdownAppend "notes", tModified
end DictionaryCreate
private command DictionaryScan @xAdded, @xModified
-- Get list of changed files
local tTags, tPermittedFiles, tChangedFiles
put GitGetRelevantTags("dictionary") into tTags
put GitGetChangedFiles("dictionary", the first line of tTags, \
the last line of tTags) into tChangedFiles
-- Check whether each entry was added or just modified
local tFile