This repository was archived by the owner on Aug 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathrelease_notes_builder.livecodescript
More file actions
1452 lines (1196 loc) · 46.4 KB
/
release_notes_builder.livecodescript
File metadata and controls
1452 lines (1196 loc) · 46.4 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"
constant kExtensionLcbTypes = "widgets,libraries,modules"
constant kExtensionLcbStrings = "widget,library,module"
constant kExtensionScriptTypes = "script-libraries"
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 & "edition_utilities.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
end Finalize
command releaseNotesBuilderRun pEdition, pVersion, pReleaseType, pOutputDir
local tError
builderLog "report", "Building release notes for version" && pVersion
try
Initialize pVersion, pOutputDir
BaseCreate
local tComponents
put builderComponentFolders() into tComponents
-- special case some extra folders
local tIndex
put the number of elements of tComponents + 1 into tIndex
put builderRepoFolder() & "/docs" into tComponents[tIndex]["folder"]
put "engine" into tComponents[tIndex]["metadata"]["category"]
put "community" into tComponents[tIndex]["metadata"]["edition"]
add 1 to tIndex
put builderRepoFolder() & "/ide" into tComponents[tIndex]["folder"]
put "ide" into tComponents[tIndex]["metadata"]["category"]
put "community" into tComponents[tIndex]["metadata"]["edition"]
put the number of elements of tComponents + 1 into tIndex
put builderRepoFolder() & "/docs/lcb" into tComponents[tIndex]["folder"]
put "builder" into tComponents[tIndex]["metadata"]["category"]
put "" into tComponents[tIndex]["metadata"]["edition"]
local tPath, tTypePath
local tReposA, tDefaultEdition
put builderPrivateRepoFolder() into tReposA["indy"]
put builderRepoFolder() into tReposA["community"]
repeat for each key tDefaultEdition in tReposA
put tReposA[tDefaultEdition] into tPath
repeat for each item tType in kExtensionLcbTypes,kExtensionScriptTypes
put tPath & slash & "extensions" & slash & tType into tTypePath
if there is not a folder tTypePath then
next repeat
end if
repeat for each line tFolder in FileGetSubFolders(tTypePath)
put the number of elements of tComponents + 1 into tIndex
put tFolder into tComponents[tIndex]["folder"]
put "extension" into tComponents[tIndex]["metadata"]["category"]
local tEdition
put ExtensionsGetEdition(tFolder) into tEdition
if tEdition is empty then
put tDefaultEdition into tEdition
end if
put tEdition into tComponents[tIndex]["metadata"]["edition"]
put ExtensionsGetSectionName(tFolder) into tComponents[tIndex]["metadata"]["section"]
end repeat
end repeat
end repeat
ComponentsCreate tComponents
DictionaryCreate tComponents
PreviousCreate
OutputNotes
OutputUpdates
Finalize
return builderFileGetUTF8Contents(OutputGetUpdatesFilename("html"))
catch tError
put tError
builderLog "error", tError
end try
end releaseNotesBuilderRun
private function PathGetLastComponent pPath
set the itemdelimiter to slash
return item -1 of pPath
end PathGetLastComponent
private command ComponentsCreateForEdition pEdition, pCollated, pBugInfo
local tDisplayName
if pEdition is not empty then
put editionDisplayName(pEdition) into tDisplayName
end if
repeat for each item tCategory in builderComponentCategories()
local tTitle
if tDisplayName is not empty then
put merge("# LiveCode [[ tDisplayName ]] [[ tCategory ]] changes") into tTitle
else
put merge("# LiveCode [[ tCategory ]] changes") into tTitle
end if
if NotesHaveContent(pCollated[pEdition][tCategory], pBugInfo[pEdition][tCategory]) then
MarkdownAppend "notes", tTitle
MarkdownAppend "updates", tTitle
NotesGenerate tCategory, pCollated[pEdition][tCategory], 1
BugGenerate pBugInfo[pEdition][tCategory], tCategory
end if
end repeat
end ComponentsCreateForEdition
private command ComponentsCreate pComponents
BuilderLog "report", "Creating component release notes"
local tCollated, tBugInfo
repeat for each element tComponent in pComponents
ComponentsCreatePath tComponent, tCollated, tBugInfo
end repeat
repeat for each item tEdition in editionNames()
ComponentsCreateForEdition tEdition, tCollated, tBugInfo
end repeat
ComponentsCreateForEdition "", tCollated, tBugInfo
end ComponentsCreate
private command ComponentsCreatePath pComponent, @xCollated, @xBugInfo
local tSkip = true
try
put there is not a folder (pComponent["folder"] & "/notes") into tSkip
end try
set the itemDelimiter to slash
if tSkip then
builderLog "report", "Skipping no notes for component" && the last item of pComponent["folder"]
exit ComponentsCreatePath
end if
builderLog "report", "Creating release notes for" && the last item of pComponent["folder"]
if pComponent["metadata"]["section"] is empty then
-- just because something is a LCB module or a script library does not mean
-- we necessarily want to give it a separate section in the release notes
-- for example a widget or library for use purely within the IDE should
-- just merge into the IDE notes.
ScanAndCollatePath \
pComponent["folder"], \
xCollated[pComponent["metadata"]["edition"]][pComponent["metadata"]["category"]], \
xBugInfo[pComponent["metadata"]["edition"]][pComponent["metadata"]["category"]]
else
local tCollatedSection, tToMerge
ScanAndCollatePath \
pComponent["folder"], \
tCollatedSection, \
xBugInfo[pComponent["metadata"]["edition"]][pComponent["metadata"]["category"]]
-- Note the use of AppendToSection here means multiple components can be merged
-- into the same sub-section of the notes. For example, engine -> Mobile or
-- ide -> Script Editor
put 1 into tToMerge["__count"]
put tCollatedSection into tToMerge[1]
put pComponent["metadata"]["section"] into tToMerge[1]["__name"]
if NotesHaveContent(tCollatedSection) then
NotesMerge \
tToMerge, \
xCollated[pComponent["metadata"]["edition"]][pComponent["metadata"]["category"]]
end if
end if
end ComponentsCreatePath
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]]")
builderFileSetUTF8Contents tPath, sMarkdownText["notes"]
end OutputNotesMarkdown
private command OutputNotesGuide
local tPath
put OutputGetGuideFilename() into tPath
builderLog "report", merge("Writing [[tPath]]")
builderFileSetUTF8Contents 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 builderFileGetUTF8Contents(tHtmlTemplatePath) into tHtml
replace "@MARKDOWN@" with tEscaped in tHtml
local tOutpath
put OutputGetNotesFilename("html") into tOutpath
builderLog "report", merge("Writing [[tOutpath]]")
builderFileSetUTF8Contents 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]]")
builderFileSetUTF8Contents tPath, sMarkdownText["updates"]
end OutputUpdatesMarkdown
// Find <h(n)> elements and make them <h(n+pAmount)>
private command MakeHeadersSmaller @xHTML, pAmount
local tStart, tEnd, tOldEnd, tNumber
put 1 into tOldEnd
repeat while matchChunk(char tOldEnd to -1 of xHTML, \
"<(?:\/)?h([0-9]+)>", tStart, tEnd)
add tOldEnd - 1 to tEnd
add tOldEnd - 1 to tStart
put char tStart to tEnd of xHTML into tNumber
add pAmount to tNumber
put tNumber into char tStart to tEnd of xHTML
put tEnd into tOldEnd
add the number of chars in tNumber - (tEnd - tStart) + 1 \
to tOldEnd
end repeat
end MakeHeadersSmaller
// Find (at least) double blank lines between specified tags
// and add empty paragraph
private command RespectBlankLinesBetween @xHTML, pFromTagSuffix, pToTagPrefix
put replaceText(xHtml, pFromTagSuffix & ">\n{2,}<" & pToTagPrefix, \
pFromTagSuffix & ">" & return & "<p> </p>" & \
return & "<" & pToTagPrefix) into xHtml
end RespectBlankLinesBetween
private command EnsureMergMarkdown
builderExtUnpack "Community"
local tMergMarkdown
put builderUnpackFolder("Community") & slash & "Ext" into tMergMarkdown
local tFolders
put folders(tMergMarkdown) into tFolders
filter tFolders with "mergMarkdown*"
if tFolders is empty then
builderLog "error", "couldn't download mergMarkdown"
end if
put slash & line 1 of tFolders & slash & "mergMarkdown" after tMergMarkdown
switch the platform
case "macos"
put ".bundle" after tMergMarkdown
break
case "linux"
if the processor is "x86_64" then
put "-x64.so" after tMergMarkdown
else
put ".so" after tMergMarkdown
end if
break
case "win32"
if the processor is "x86_64" then
put "-x86_64.dll" after tMergMarkdown
else
put "-x86.dll" after tMergMarkdown
end if
break
end switch
set the externals of the templatestack to tMergMarkdown
lock messages
create stack
unlock messages
start using it
end EnsureMergMarkdown
command OutputUpdatesHtml
local tHtml
EnsureMergMarkdown
put mergMarkdownToXHTML(sMarkdownText["updates"] \
,true,true,true,true,false,true,true,true) into tHtml
// Shrink all headers by 2
MakeHeadersSmaller tHTML, 2
// Add extra blank paragraphs between any tag &
// subsequent header
RespectBlankLinesBetween tHTML, "", "h"
// Add extra blank paragraphs between paragraphs
RespectBlankLinesBetween tHTML, "p", "p"
local tPath
put OutputGetUpdatesFilename("html") into tPath
builderLog "report", merge("Writing [[tPath]]")
builderFileSetUTF8Contents tPath, tHtml
end OutputUpdatesHtml
----------------------------------------------------------------
-- Base file sections
----------------------------------------------------------------
private command BaseCreate
builderLog "report", "Creating base release notes"
BaseCreateTitle
BaseCreateContents
BaseCreateOverview
BaseCreateIssues
MarkdownAppend "notes", BaseReadFile("breaking_changes")
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("base") & slash & pBasename & ".md" into tPath
if there is a file tPath then
return builderFileGetUTF8Contents(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
/*
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 command NotesScan pType, @xScan
local tTags, tNumTags
put GitGetRelevantTags(pType) into tTags
put the number of lines in tTags into tNumTags
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
NotesScanVersion pType, tVersion, tVersionFiles, xScan
add 1 to tTagOffset
end repeat
end NotesScan
private command NotesScanVersion 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 builderFileGetUTF8Contents(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
else
-- filter out touched files with version headers
set the itemDelimiter to "-"
if item 1 of tVersion is not item 1 of pVersion then
builderLog "report", "Filtering touched file with version header" && tFile
next repeat
end if
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 NotesScanVersion
----------------------------------------------------------------
-- New-style notes
----------------------------------------------------------------
/*
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 NotesCollate pType, pScanInfo, @xCollated, @xBugInfo
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]
NotesCollateFile pType, tVersion, pScanInfo[tVersion][tFile], xCollated, xBugInfo
end repeat
subtract 1 from tTagCount
end repeat
end NotesCollate
private command NotesCollateFile 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
local tLineCount
put the number of lines of pFileInfo["markdown"] into tLineCount
repeat for each line tLine in pFileInfo["markdown"]
-- First check if this is a bug info line
local tOnlyAddBug
NotesExtractBugInfo tBasename, tLine, tLineCount, tBugId, tBugDesc
put it into tOnlyAddBug
if tBugId is not empty then
BugAddInfo xBugInfo, pVersion, tBugId, tBugDesc
if tOnlyAddBug then
next repeat
else
put empty into tBugId
put empty into tBugDesc
end if
end if
-- Second, check if it's a section control line
put tLevel into tOldLevel
NotesExtractSectionInfo 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
NotesUpdateSectionPath 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 NotesCollateFile
private command NotesUpdateSectionPath pLevel, pName, @xCollated, @xSectionPath
if pLevel is 0 then
exit NotesUpdateSectionPath
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 NotesUpdateSectionPath
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 NotesUpdateSectionPath
private command NotesExtractBugInfo pBaseName, pLine, pLineCount, @rId, @rDesc
-- v2 notes are `# [int] description`
if matchText(pLine, "^\s*#\s+\[(\w*)\]\s+(.*)$", rId, rDesc) then
return true for value
end if
-- check if it's a v1 single line bugfix note
split pBaseName with "-"
if pBaseName[2] is an integer and \
word 1 of pLine is "#" then
put pBaseName[2] into rId
put word 2 to -1 of pLine into rDesc
end if
return pLineCount is 1 for value
end NotesExtractBugInfo
private command NotesExtractSectionInfo 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 NotesExtractSectionInfo
private command NotesMerge pLeftCollated, @xRightCollated
if pLeftCollated is not an array then
exit NotesMerge
end if
if word 1 to -1 of pLeftCollated["__markdown"] is not empty then
if xRightCollated["__markdown"] is empty then
put return & return after xRightCollated["__markdown"]
end if
put pLeftCollated["__markdown"] after xRightCollated["__markdown"]
end if
local tLeftID, tRightID
repeat with tLeftID = 1 to pLeftCollated["__count"]
local tNameFound
put false into tNameFound
repeat with tRightID = 1 to xRightCollated["__count"]
if pLeftCollated[tLeftID]["__name"] is xRightCollated[tRightID]["__name"] then
NotesMerge pLeftCollated[tLeftID], xRightCollated[tRightID]
put true into tNameFound
end if
end repeat
if not tNameFound then
add 1 to xRightCollated["__count"]
put pLeftCollated[tLeftID] into xRightCollated[xRightCollated["__count"]]
end if
end repeat
end NotesMerge
private function NotesHaveContent pCollated, pBugInfo
if pCollated is an array then
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 NotesHaveContent(pCollated[tId]) then
return true
end if
end repeat
end if
if pBugInfo is an array and the number of elements in pBugInfo > 0 then
return true
end if
return false
end NotesHaveContent
private command NotesGenerate pType, pCollated, pLevel
MarkdownAppend "notes", NotesGenerateContent(pType, pCollated, pLevel)
MarkdownAppend "updates", NotesGenerateContent(pType, pCollated, pLevel)
end NotesGenerate
-- If <pOnlyCurrent> is true, then only generate content that's brand new in
-- the release that notes are being generated for
private function NotesGenerateContent 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 NotesGenerateContent(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 NotesGenerateContent
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, kExtensionLcbTypes) into tTypeOffset
put item tTypeOffset of kExtensionLcbStrings 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
if there is a file ExtensionsGetManifestPath(pExtPath) then
return ExtensionsGetLCBName(pExtPath)
else if there is a file ExtensionsGetDocPath(pExtPath) then
-- Script library
return ExtensionsGetLCSName(pExtPath)
end if
return empty
end ExtensionsGetName
private function ExtensionsGetLCSName pExtPath
return ExtensionGetSingleLineElement(pExtPath, "Title")
end ExtensionsGetLCSName
private function ExtensionsGetEdition pExtPath
if there is a file ExtensionsGetDocPath(pExtPath) then
return ExtensionGetSingleLineElement(pExtPath, "Edition")
end if
return empty
end ExtensionsGetEdition
private function ExtensionGetSingleLineElement pExtPath, pElement
local tDoc
put builderFileGetContents(ExtensionsGetDocPath(pExtPath)) into tDoc
local tRegex
put "(?i)^" & pElement &":\s+(.*)" into tRegex
local tValue
repeat for each line tLine in tDoc
get matchText(word 1 to -1 of tLine, tRegex, tValue)
if tValue is not empty then
return tValue
end if
end repeat
return empty
end ExtensionGetSingleLineElement
private function ExtensionsGetLCBName pExtPath
local tManifest, tXmlId
put builderFileGetContents(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
revXMLDeleteTree tXmlId
if tTargetName begins with "xmlerr" then
return empty
end if
return tTargetName
end ExtensionsGetLCBName
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
if there is no file merge("[[pExtPath]]/[[tShortName]].lcb") then
return ExtensionsGetLCSKind(pExtPath, tShortName)
else
return ExtensionsGetLCBKind(pExtPath, tShortName)
end if
end ExtensionsGetKind
private function ExtensionsGetLCSKind pExtPath, pShortName
local tSource, tLine, tName
if there is no file merge("[[pExtPath]]/[[pShortName]].livecodescript") then
return empty
end if
put builderFileGetUTF8Contents(merge("[[pExtPath]]/[[pShortName]].livecodescript")) into tSource
repeat for each line tLine in tSource
get matchText(tLine, "(?i)^script" && quote & "(.*)" & quote, tName)
if tName is not empty then
exit repeat
end if
end repeat
return tName
end ExtensionsGetLCSKind
private function ExtensionsGetLCBKind pExtPath, pShortName
local tLcbSource, tLine, tModuleName
put builderFileGetUTF8Contents(merge("[[pExtPath]]/[[pShortName]].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 ExtensionsGetLCBKind
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
private function ExtensionsGetDocPath pExtPath
local tDocFile
put FileGetPath("built-extensions") into tDocFile
put slash & ExtensionsGetKind(pExtPath) after tDocFile
put slash & "api.lcdoc" after tDocFile
return tDocFile
end ExtensionsGetDocPath
private command ScanAndCollatePath pExtPath, @xCollated, @xBugInfo
local tNotesPath
put merge("[[pExtPath]]/notes") into tNotesPath
if there is not a folder tNotesPath then
exit ScanAndCollatePath
end if
local tScan
NotesScan tNotesPath, tScan
NotesCollate pExtPath, tScan, xCollated, xBugInfo
end ScanAndCollatePath
----------------------------------------------------------------
-- Dictionary change generation
----------------------------------------------------------------
private command DictionaryCreate pComponents
builderLog "report", "Creating dictionary release notes"
local tAdded, tModified
DictionaryScan pComponents, tAdded, tModified