@@ -3130,6 +3130,116 @@ export function exportCppAsync(parsed: commandParser.ParsedCommand) {
3130
3130
} )
3131
3131
}
3132
3132
3133
+ export function downloadDiscourseTagAsync ( parsed : commandParser . ParsedCommand ) : Promise < void > {
3134
+ const rx = / ` ` ` c o d e c a r d ( ( .| \s ) * ) ` ` ` / ;
3135
+ const tag = parsed . args [ 0 ] as string ;
3136
+ if ( ! tag )
3137
+ U . userError ( "Missing tag" )
3138
+ const out = parsed . flags [ "out" ] as string || "temp" ;
3139
+ const outmd = parsed . flags [ "md" ] as string ;
3140
+ const discourseRoot = pxt . appTarget . appTheme
3141
+ && pxt . appTarget . appTheme . socialOptions
3142
+ && pxt . appTarget . appTheme . socialOptions . discourse ;
3143
+ if ( ! discourseRoot )
3144
+ U . userError ( "Target not configured for discourse" ) ;
3145
+ if ( outmd && ! fs . existsSync ( outmd ) )
3146
+ U . userError ( `${ outmd } file not found` )
3147
+ let md : string = outmd && fs . readFileSync ( outmd , { encoding : "utf8" } ) ;
3148
+
3149
+ nodeutil . mkdirP ( out ) ;
3150
+ let n = 0 ;
3151
+ let cards : pxt . CodeCard [ ] = [ ] ;
3152
+ // parse existing cards
3153
+ if ( md ) {
3154
+ md . replace ( rx , ( m , c ) => {
3155
+ cards = JSON . parse ( c ) ;
3156
+ return "" ;
3157
+ } )
3158
+ }
3159
+ return pxt . discourse . topicsByTag ( discourseRoot , tag )
3160
+ . then ( topics => Promise . mapSeries ( topics , topic => {
3161
+ pxt . log ( ` ${ topic . title } ` )
3162
+ return pxt . discourse . extractSharedIdFromPostUrl ( topic . url )
3163
+ . then ( id => {
3164
+ if ( ! id ) {
3165
+ pxt . log ( ` --> unknown project id` )
3166
+ return Promise . resolve ( ) ;
3167
+ }
3168
+ n ++ ;
3169
+ return extractAsyncInternal ( id , out , false )
3170
+ . then ( ( ) => {
3171
+ // does the current card have an image?
3172
+ let card = cards . filter ( c => c . url == topic . url ) [ 0 ] ;
3173
+ if ( card && card . imageUrl ) {
3174
+ pxt . log ( `${ card . name } already in markdown` )
3175
+ return Promise . resolve ( ) ; // already handled
3176
+ }
3177
+ // new card? add to list
3178
+ if ( ! card ) {
3179
+ card = topic ;
3180
+ card . description = "" ;
3181
+ cards . push ( card ) ;
3182
+ }
3183
+
3184
+ const pfn = `./docs/static/discourse/${ id } .` ;
3185
+ if ( md && ! [ "png" , "jpg" , "gif" ] . some ( ext => nodeutil . fileExistsSync ( pfn + ext ) ) ) {
3186
+ return downloadImageAsync ( id , topic , `https://makecode.com/api/${ id } /thumb` )
3187
+ . catch ( e => {
3188
+ // no image
3189
+ pxt . debug ( `no thumb ${ e } ` ) ;
3190
+ // use image from forum
3191
+ if ( topic . imageUrl )
3192
+ return downloadImageAsync ( id , topic , topic . imageUrl ) ;
3193
+ else
3194
+ throw e ; // bail out
3195
+ } )
3196
+ }
3197
+ return Promise . resolve ( ) ;
3198
+ } ) . catch ( e => {
3199
+ pxt . log ( `error: project ${ id } could not be loaded or no image` ) ;
3200
+ } ) ;
3201
+ } )
3202
+ } ) )
3203
+ . then ( ( ) => {
3204
+ if ( md ) {
3205
+ // inject updated cards
3206
+ cards . forEach ( card => delete ( card as any ) . id ) ;
3207
+ md = md . replace ( rx , ( m , c ) => {
3208
+ return `\`\`\`codecard
3209
+ ${ JSON . stringify ( cards , null , 4 ) }
3210
+ \`\`\`` ;
3211
+ } )
3212
+ fs . writeFileSync ( outmd , md , { encoding : "utf8" } ) ;
3213
+ }
3214
+ pxt . log ( `downloaded ${ n } programs from tag ${ tag } ` )
3215
+ } )
3216
+
3217
+ function downloadImageAsync ( id : string , topic : pxt . CodeCard , url : string ) : Promise < void > {
3218
+ return pxt . Util . requestAsync ( {
3219
+ url : `https://makecode.com/api/${ id } /thumb` ,
3220
+ method : "GET" ,
3221
+ responseArrayBuffer : true ,
3222
+ headers : {
3223
+ "accept" : "image/*"
3224
+ }
3225
+ } ) . then ( resp => {
3226
+ if ( resp . buffer ) {
3227
+ const m = / i m a g e \/ ( p n g | j p e g | g i f ) / . exec ( resp . headers [ "content-type" ] as string ) ;
3228
+ if ( ! m ) {
3229
+ pxt . log ( `unknown image type: ${ resp . headers [ "content-type" ] } ` ) ;
3230
+ } else {
3231
+ let ext = m [ 1 ] ;
3232
+ if ( ext == "jpeg" ) ext = "jpg" ;
3233
+ const ifn = `/static/discourse/${ id } .${ ext } ` ;
3234
+ const localifn = "./docs" + ifn ;
3235
+ topic . imageUrl = ifn ;
3236
+ nodeutil . writeFileSync ( localifn , new Buffer ( resp . buffer as ArrayBuffer ) ) ;
3237
+ }
3238
+ }
3239
+ } ) ;
3240
+ }
3241
+ }
3242
+
3133
3243
export function formatAsync ( parsed : commandParser . ParsedCommand ) {
3134
3244
let inPlace = ! ! parsed . flags [ "i" ] ;
3135
3245
let testMode = ! ! parsed . flags [ "t" ] ;
@@ -4682,7 +4792,8 @@ export function extractAsync(parsed: commandParser.ParsedCommand): Promise<void>
4682
4792
const vscode = ! ! parsed . flags [ "code" ] ;
4683
4793
const out = parsed . flags [ "code" ] || '.' ;
4684
4794
const filename = parsed . args [ 0 ] ;
4685
- return extractAsyncInternal ( filename , out as string , vscode ) ;
4795
+ return extractAsyncInternal ( filename , out as string , vscode )
4796
+ . then ( ( ) => { } ) ;
4686
4797
}
4687
4798
4688
4799
function isScriptId ( id : string ) {
@@ -4722,13 +4833,13 @@ function fetchTextAsync(filename: string): Promise<Buffer> {
4722
4833
return readFileAsync ( filename )
4723
4834
}
4724
4835
4725
- function extractAsyncInternal ( filename : string , out : string , vscode : boolean ) : Promise < void > {
4836
+ function extractAsyncInternal ( filename : string , out : string , vscode : boolean ) : Promise < string [ ] > {
4726
4837
if ( filename && nodeutil . existsDirSync ( filename ) ) {
4727
4838
pxt . log ( `extracting folder ${ filename } ` ) ;
4728
4839
return Promise . all ( fs . readdirSync ( filename )
4729
4840
. filter ( f => / \. ( h e x | u f 2 ) / . test ( f ) )
4730
4841
. map ( f => extractAsyncInternal ( path . join ( filename , f ) , out , vscode ) ) )
4731
- . then ( ( ) => { } ) ;
4842
+ . then ( ( ) => [ filename ] ) ;
4732
4843
}
4733
4844
4734
4845
return fetchTextAsync ( filename )
@@ -4738,6 +4849,7 @@ function extractAsyncInternal(filename: string, out: string, vscode: boolean): P
4738
4849
pxt . debug ( 'launching code...' )
4739
4850
dirs . forEach ( dir => openVsCode ( dir ) ) ;
4740
4851
}
4852
+ return dirs ;
4741
4853
} )
4742
4854
}
4743
4855
@@ -4777,31 +4889,31 @@ function extractBufferAsync(buf: Buffer, outDir: string): Promise<string[]> {
4777
4889
. then ( ( ) => {
4778
4890
let str = buf . toString ( "utf8" )
4779
4891
if ( str [ 0 ] == ":" ) {
4780
- console . log ( "Detected .hex file." )
4892
+ pxt . debug ( "Detected .hex file." )
4781
4893
return unpackHexAsync ( buf )
4782
4894
} else if ( str [ 0 ] == "U" ) {
4783
- console . log ( "Detected .uf2 file." )
4895
+ pxt . debug ( "Detected .uf2 file." )
4784
4896
return unpackHexAsync ( buf )
4785
4897
} else if ( str [ 0 ] == "{" ) { // JSON
4786
- console . log ( "Detected .json file." )
4898
+ pxt . debug ( "Detected .json file." )
4787
4899
return JSON . parse ( str )
4788
4900
} else if ( buf [ 0 ] == 0x5d ) { // JSZ
4789
- console . log ( "Detected .jsz/.pxt file." )
4901
+ pxt . debug ( "Detected .jsz/.pxt file." )
4790
4902
return pxt . lzmaDecompressAsync ( buf as any )
4791
4903
. then ( str => JSON . parse ( str ) )
4792
4904
} else
4793
4905
return Promise . resolve ( null )
4794
4906
} )
4795
4907
. then ( json => {
4796
4908
if ( ! json ) {
4797
- console . log ( "Couldn't extract." )
4909
+ pxt . log ( "Couldn't extract." )
4798
4910
return undefined ;
4799
4911
}
4800
4912
if ( json . meta && json . source ) {
4801
4913
json = typeof json . source == "string" ? JSON . parse ( json . source ) : json . source
4802
4914
}
4803
4915
if ( Array . isArray ( json . scripts ) ) {
4804
- console . log ( "Legacy TD workspace." )
4916
+ pxt . debug ( "Legacy TD workspace." )
4805
4917
json . projects = json . scripts . map ( ( scr : any ) => ( {
4806
4918
name : scr . header . name ,
4807
4919
files : oneFile ( scr . source , scr . header . editor )
@@ -4810,8 +4922,8 @@ function extractBufferAsync(buf: Buffer, outDir: string): Promise<string[]> {
4810
4922
}
4811
4923
4812
4924
if ( json [ pxt . CONFIG_NAME ] ) {
4813
- console . log ( "Raw JSON files." )
4814
- let cfg : pxt . PackageConfig = JSON . parse ( json [ pxt . CONFIG_NAME ] )
4925
+ pxt . debug ( "Raw JSON files." )
4926
+ let cfg : pxt . PackageConfig = pxt . Package . parseAndValidConfig ( json [ pxt . CONFIG_NAME ] )
4815
4927
let files = json
4816
4928
json = {
4817
4929
projects : [ {
@@ -4823,7 +4935,7 @@ function extractBufferAsync(buf: Buffer, outDir: string): Promise<string[]> {
4823
4935
4824
4936
let prjs : SavedProject [ ] = json . projects
4825
4937
if ( ! prjs ) {
4826
- console . log ( "No projects found." )
4938
+ pxt . log ( "No projects found." )
4827
4939
return undefined ;
4828
4940
}
4829
4941
const dirs = writeProjects ( prjs , outDir )
@@ -6148,6 +6260,26 @@ ${pxt.crowdin.KEY_VARIABLE} - crowdin key
6148
6260
argString : "<target-directory>"
6149
6261
} , exportCppAsync ) ;
6150
6262
6263
+ p . defineCommand ( {
6264
+ name : "downloaddiscoursetag" ,
6265
+ aliases : [ "ddt" ] ,
6266
+ help : "Download program for a discourse tag" ,
6267
+ advanced : true ,
6268
+ argString : "<tag>" ,
6269
+ flags : {
6270
+ out : {
6271
+ description : "output folder, default is temp" ,
6272
+ argument : "out" ,
6273
+ type : "string"
6274
+ } ,
6275
+ md : {
6276
+ description : "path of the markdown file to generate" ,
6277
+ argument : "out" ,
6278
+ type : "string"
6279
+ }
6280
+ }
6281
+ } , downloadDiscourseTagAsync )
6282
+
6151
6283
function simpleCmd ( name : string , help : string , callback : ( c ?: commandParser . ParsedCommand ) => Promise < void > , argString ?: string , onlineHelp ?: boolean ) : void {
6152
6284
p . defineCommand ( { name, help, onlineHelp, argString } , callback ) ;
6153
6285
}
0 commit comments