Fossil SCM

Add artifact_to_json() support.

stephan 2025-03-24 18:05 trunk merge
Commit 1e2d60287a002c69812d6ad2f03fec0df1f68d98c1aff89d247b9fb685a0d378
2 files changed +2 +294
+2
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
16291629
sqlite3_create_function(db, "chat_msg_from_event", 4,
16301630
SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
16311631
chat_msg_from_event, 0, 0);
16321632
sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
16331633
file_inode_sql_func,0,0);
1634
+ sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635
+ artifact_to_json_sql_func,0,0);
16341636
16351637
}
16361638
16371639
#if USE_SEE
16381640
/*
16391641
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
 
 
1634
1635 }
1636
1637 #if USE_SEE
1638 /*
1639
--- src/db.c
+++ src/db.c
@@ -1629,10 +1629,12 @@
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
1634 sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
1635 artifact_to_json_sql_func,0,0);
1636
1637 }
1638
1639 #if USE_SEE
1640 /*
1641
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,299 @@
29112911
if( g.argc!=3 ) usage("RECORDID");
29122912
rid = name_to_rid(g.argv[2]);
29132913
content_get(rid, &content);
29142914
manifest_crosslink(rid, &content, MC_NONE);
29152915
}
2916
+
2917
+/*
2918
+** For a given CATYPE_... value, returns a human-friendly name, or
2919
+** NULL if typeId is unknown or is CFTYPE_ANY.
2920
+*/
2921
+const char * artifact_type_to_name(int typeId){
2922
+ switch(typeId){
2923
+ case CFTYPE_MANIFEST: return "checkin";
2924
+ case CFTYPE_CLUSTER: return "cluster";
2925
+ case CFTYPE_CONTROL: return "tag";
2926
+ case CFTYPE_WIKI: return "wiki";
2927
+ case CFTYPE_TICKET: return "ticket";
2928
+ case CFTYPE_ATTACHMENT: return "attachment";
2929
+ case CFTYPE_EVENT: return "event";
2930
+ case CFTYPE_FORUM: return "forumpost";
2931
+ }
2932
+ return NULL;
2933
+}
2934
+
2935
+/*
2936
+** Creates a JSON representation of p, appending it to b.
2937
+**
2938
+** b is not cleared before rendering, so the caller needs to do that
2939
+** if it's important for their use case.
2940
+**
2941
+** Pedantic note: this routine traverses p->aFile directly, rather than
2942
+** using manifest_file_next(), so that delta manifests are rendered as-is
2943
+** instead of having their derived files. If that policy is ever changed,
2944
+** p will need to be non-const.
2945
+*/
2946
+void artifact_to_json(Manifest const *p, Blob *b){
2947
+ int i;
2948
+ char *zUuid;
2949
+
2950
+ blob_append_literal(b, "{");
2951
+ zUuid = rid_to_uuid(p->rid);
2952
+ blob_appendf(b, "\"uuid\": %!j", zUuid);
2953
+ /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
2954
+ blob_appendf(b, ", \"type\": %!j", artifact_type_to_name(p->type));
2955
+#define ISA(TYPE) if( p->type==TYPE )
2956
+#define CARD_LETTER(LETTER) \
2957
+ blob_append_literal(b, ",\"" #LETTER "\": ")
2958
+#define CARD_STR(LETTER, VAL) \
2959
+ assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
2960
+#define CARD_STR2(LETTER, VAL) \
2961
+ if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
2962
+#define STR_OR_NULL(VAL) \
2963
+ if( VAL ) blob_appendf(b, "%!j", VAL); \
2964
+ else blob_append(b, "null", 4)
2965
+#define KVP_STR(ADDCOMMA, KEY,VAL) \
2966
+ if(ADDCOMMA) blob_append_char(b, ','); \
2967
+ blob_appendf(b, "%!j: ", #KEY); \
2968
+ STR_OR_NULL(VAL)
2969
+
2970
+ /* Noting that only 1 (at most) of the A-card pieces will be non-NULL... */
2971
+ ISA( CFTYPE_ATTACHMENT ){
2972
+ CARD_LETTER(A);
2973
+ blob_append_char(b, '{');
2974
+ KVP_STR(0, filename, p->zAttachName);
2975
+ KVP_STR(1, target, p->zAttachTarget);
2976
+ KVP_STR(1, source, p->zAttachSrc);
2977
+ blob_append_char(b, '}');
2978
+ }
2979
+ CARD_STR2(B, p->zBaseline);
2980
+ CARD_STR2(C, p->zComment);
2981
+ CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
2982
+ ISA( CFTYPE_EVENT ){
2983
+ blob_appendf(b, ", \"E\": {\"time\": %f, \"id\": %!j}",
2984
+ p->rEventDate, p->zEventId);
2985
+ }
2986
+ ISA( CFTYPE_MANIFEST ){
2987
+ CARD_LETTER(F);
2988
+ blob_append_char(b, '[');
2989
+ for( i = 0; i < p->nFile; ++i ){
2990
+ ManifestFile const * const pF = &p->aFile[i];
2991
+ if( i>0 ) blob_append_char(b, ',');
2992
+ blob_append_char(b, '{');
2993
+ KVP_STR(0, name, pF->zName);
2994
+ KVP_STR(1, uuid, pF->zUuid);
2995
+ KVP_STR(1, perm, pF->zPerm);
2996
+ KVP_STR(1, oldName, pF->zPrior);
2997
+ blob_append_char(b, '}');
2998
+ }
2999
+ /* Special case: model checkins with no F-card as having an empty
3000
+ ** array, rather than no F-cards, to hypothetically simplify
3001
+ ** handling in JSON queries. */
3002
+ blob_append_char(b, ']');
3003
+ }
3004
+ CARD_STR2(G, p->zThreadRoot);
3005
+ CARD_STR2(H, p->zThreadTitle);
3006
+ CARD_STR2(I, p->zInReplyTo);
3007
+ if( p->nField ){
3008
+ CARD_LETTER(J);
3009
+ blob_append_char(b, '[');
3010
+ for( i = 0; i < p->nField; ++i ){
3011
+ if( i>0 ) blob_append_char(b, ',');
3012
+ blob_append_char(b, '{');
3013
+ KVP_STR(0, name, p->aField[i].zName);
3014
+ KVP_STR(1, value, p->aField[i].zValue);
3015
+ blob_append_char(b, '}');
3016
+ }
3017
+ blob_append_char(b, ']');
3018
+ }
3019
+ CARD_STR2(K, p->zTicketUuid);
3020
+ CARD_STR2(L, p->zWikiTitle);
3021
+ ISA( CFTYPE_CLUSTER && p->nCChild>0 ){
3022
+ CARD_LETTER(M);
3023
+ blob_append_char(b, '[');
3024
+ for( int i = 0; i < p->nCChild; ++i ){
3025
+ if( i>0 ) blob_append_char(b, ',');
3026
+ blob_appendf(b, "%!j", p->azCChild[i]);
3027
+ }
3028
+ blob_append_char(b, ']');
3029
+ }
3030
+ CARD_STR2(N, p->zMimetype);
3031
+ ISA( CFTYPE_MANIFEST ){
3032
+ CARD_LETTER(P);
3033
+ blob_append_char(b, '[');
3034
+ if( p->nParent ){
3035
+ for( i = 0; i < p->nParent; ++i ){
3036
+ if( i>0 ) blob_append_char(b, ',');
3037
+ blob_appendf(b, "%!j", p->azParent[i]);
3038
+ }
3039
+ }
3040
+ /* Special case: model checkins with no P-card as having
3041
+ ** an empty array, rather than no P-card, to hypothetically
3042
+ ** simplify handling in JSON queries. */
3043
+ blob_append_char(b, ']');
3044
+ }
3045
+ if( p->nCherrypick ){
3046
+ CARD_LETTER(Q);
3047
+ blob_append_char(b, '[');
3048
+ for( i = 0; i < p->nCherrypick; ++i ){
3049
+ if( i>0 ) blob_append_char(b, ',');
3050
+ blob_append_char(b, '{');
3051
+ blob_appendf(b, "\"type\": \"%c\"", p->aCherrypick[i].zCPTarget[0]);
3052
+ KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
3053
+ KVP_STR(1, base, p->aCherrypick[i].zCPBase);
3054
+ blob_append_char(b, '}');
3055
+ }
3056
+ blob_append_char(b, ']');
3057
+ }
3058
+ CARD_STR2(R, p->zRepoCksum);
3059
+ if( p->nTag ){
3060
+ CARD_LETTER(T);
3061
+ blob_append_char(b, '[');
3062
+ for( int i = 0; i < p->nTag; ++i ){
3063
+ const char *zName = p->aTag[i].zName;
3064
+ if( i>0 ) blob_append_char(b, ',');
3065
+ blob_append_char(b, '{');
3066
+ blob_appendf(b, "\"type\": \"%c\"", *zName);
3067
+ ++zName;
3068
+ KVP_STR(1, name, zName);
3069
+ KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
3070
+ /* We could arguably resolve this to null. Per /chat
3071
+ discussion we don't want to resolve it to zUuid because
3072
+ that would effectively add information to the rendered
3073
+ manifest. */;
3074
+ KVP_STR(1, value, p->aTag[i].zValue);
3075
+ blob_append_char(b, '}');
3076
+ }
3077
+ blob_append_char(b, ']');
3078
+ }
3079
+ CARD_STR2(U, p->zUser);
3080
+ CARD_STR2(W, p->zWiki);
3081
+ fossil_free(zUuid);
3082
+ blob_append_literal(b, "}");
3083
+#undef CARD_FMT
3084
+#undef CARD_LETTER
3085
+#undef CARD_STR
3086
+#undef CARD_STR2
3087
+#undef ISA
3088
+#undef KVP_STR
3089
+#undef STR_OR_NULL
3090
+}
3091
+
3092
+/*
3093
+** Convenience wrapper around artifact_to_json() which expects rid to
3094
+** be the blob.rid of any artifact type. If it can load a Manifest
3095
+** with that rid, it returns rid, else it returns 0.
3096
+*/
3097
+int artifact_to_json_by_rid(int rid, Blob *pOut){
3098
+ Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3099
+ if( p ){
3100
+ artifact_to_json(p, pOut);
3101
+ manifest_destroy(p);
3102
+ }else{
3103
+ rid = 0;
3104
+ }
3105
+ return rid;
3106
+}
3107
+
3108
+/*
3109
+** Convenience wrapper around artifact_to_json() which accepts any
3110
+** artifact name which is legal for symbolic_name_to_rid(). On success
3111
+** it returns the rid of the artifact. Returns 0 if no such artifact
3112
+** exists and a negative value if the name is ambiguous.
3113
+**
3114
+** pOut is not cleared before rendering, so the caller needs to do
3115
+** that if it's important for their use case.
3116
+*/
3117
+int artifact_to_json_by_name(const char *zName, Blob *pOut){
3118
+ const int rid = symbolic_name_to_rid(zName, 0);
3119
+ return rid>0
3120
+ ? artifact_to_json_by_rid(rid, pOut)
3121
+ : rid;
3122
+}
3123
+
3124
+/*
3125
+** SQLite UDF for artifact_to_json(). Its single argument should be
3126
+** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3127
+** name, as per symbolic_name_to_rid(). If an artifact is found then
3128
+** the result of the UDF is that JSON as a string, else it evaluates
3129
+** to NULL.
3130
+*/
3131
+void artifact_to_json_sql_func(
3132
+ sqlite3_context *context,
3133
+ int argc,
3134
+ sqlite3_value **argv
3135
+){
3136
+ int rid = 0;
3137
+ Blob b = empty_blob;
3138
+
3139
+ if(1 != argc){
3140
+ goto error_usage;
3141
+ }
3142
+ switch( sqlite3_value_type(argv[0]) ){
3143
+ case SQLITE_INTEGER:
3144
+ rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3145
+ break;
3146
+ case SQLITE_TEXT:{
3147
+ const char * z = (const char *)sqlite3_value_text(argv[0]);
3148
+ if( z ){
3149
+ rid = artifact_to_json_by_name(z, &b);
3150
+ }
3151
+ break;
3152
+ }
3153
+ default:
3154
+ goto error_usage;
3155
+ }
3156
+ if( rid>0 ){
3157
+ sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3158
+ SQLITE_TRANSIENT);
3159
+ blob_reset(&b);
3160
+ }else{
3161
+ /* We should arguably error out if rid<0 (ambiguous name) */
3162
+ sqlite3_result_null(context);
3163
+ }
3164
+ return;
3165
+error_usage:
3166
+ sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3167
+ "artifact symbolic name", -1);
3168
+}
3169
+
3170
+
3171
+
3172
+/*
3173
+** COMMAND: test-artifact-to-json
3174
+**
3175
+** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
3176
+**
3177
+** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
3178
+*/
3179
+void test_manifest_to_json(void){
3180
+ int i;
3181
+ Blob b = empty_blob;
3182
+ Stmt q;
3183
+ const int bPretty = find_option("pretty",0,0)!=0;
3184
+ int nErr = 0;
3185
+
3186
+ db_find_and_open_repository(0,0);
3187
+ db_prepare(&q, "select json_pretty(:json)");
3188
+ for( i=2; i<g.argc; ++i ){
3189
+ char const *zName = g.argv[i];
3190
+ const int rc = artifact_to_json_by_name(zName, &b);
3191
+ if( rc<=0 ){
3192
+ ++nErr;
3193
+ fossil_warning("Error reading artifact %Q", zName);
3194
+ continue;
3195
+ }else if( bPretty ){
3196
+ db_bind_blob(&q, ":json", &b);
3197
+ b.nUsed = 0;
3198
+ db_step(&q);
3199
+ db_column_blob(&q, 0, &b);
3200
+ db_reset(&q);
3201
+ }
3202
+ fossil_print("%b\n", &b);
3203
+ blob_reset(&b);
3204
+ }
3205
+ db_finalize(&q);
3206
+ if( nErr ){
3207
+ fossil_warning("Error count: %d", nErr);
3208
+ }
3209
+}
29163210
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,299 @@
2911 if( g.argc!=3 ) usage("RECORDID");
2912 rid = name_to_rid(g.argv[2]);
2913 content_get(rid, &content);
2914 manifest_crosslink(rid, &content, MC_NONE);
2915 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2916
--- src/manifest.c
+++ src/manifest.c
@@ -2911,5 +2911,299 @@
2911 if( g.argc!=3 ) usage("RECORDID");
2912 rid = name_to_rid(g.argv[2]);
2913 content_get(rid, &content);
2914 manifest_crosslink(rid, &content, MC_NONE);
2915 }
2916
2917 /*
2918 ** For a given CATYPE_... value, returns a human-friendly name, or
2919 ** NULL if typeId is unknown or is CFTYPE_ANY.
2920 */
2921 const char * artifact_type_to_name(int typeId){
2922 switch(typeId){
2923 case CFTYPE_MANIFEST: return "checkin";
2924 case CFTYPE_CLUSTER: return "cluster";
2925 case CFTYPE_CONTROL: return "tag";
2926 case CFTYPE_WIKI: return "wiki";
2927 case CFTYPE_TICKET: return "ticket";
2928 case CFTYPE_ATTACHMENT: return "attachment";
2929 case CFTYPE_EVENT: return "event";
2930 case CFTYPE_FORUM: return "forumpost";
2931 }
2932 return NULL;
2933 }
2934
2935 /*
2936 ** Creates a JSON representation of p, appending it to b.
2937 **
2938 ** b is not cleared before rendering, so the caller needs to do that
2939 ** if it's important for their use case.
2940 **
2941 ** Pedantic note: this routine traverses p->aFile directly, rather than
2942 ** using manifest_file_next(), so that delta manifests are rendered as-is
2943 ** instead of having their derived files. If that policy is ever changed,
2944 ** p will need to be non-const.
2945 */
2946 void artifact_to_json(Manifest const *p, Blob *b){
2947 int i;
2948 char *zUuid;
2949
2950 blob_append_literal(b, "{");
2951 zUuid = rid_to_uuid(p->rid);
2952 blob_appendf(b, "\"uuid\": %!j", zUuid);
2953 /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
2954 blob_appendf(b, ", \"type\": %!j", artifact_type_to_name(p->type));
2955 #define ISA(TYPE) if( p->type==TYPE )
2956 #define CARD_LETTER(LETTER) \
2957 blob_append_literal(b, ",\"" #LETTER "\": ")
2958 #define CARD_STR(LETTER, VAL) \
2959 assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
2960 #define CARD_STR2(LETTER, VAL) \
2961 if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
2962 #define STR_OR_NULL(VAL) \
2963 if( VAL ) blob_appendf(b, "%!j", VAL); \
2964 else blob_append(b, "null", 4)
2965 #define KVP_STR(ADDCOMMA, KEY,VAL) \
2966 if(ADDCOMMA) blob_append_char(b, ','); \
2967 blob_appendf(b, "%!j: ", #KEY); \
2968 STR_OR_NULL(VAL)
2969
2970 /* Noting that only 1 (at most) of the A-card pieces will be non-NULL... */
2971 ISA( CFTYPE_ATTACHMENT ){
2972 CARD_LETTER(A);
2973 blob_append_char(b, '{');
2974 KVP_STR(0, filename, p->zAttachName);
2975 KVP_STR(1, target, p->zAttachTarget);
2976 KVP_STR(1, source, p->zAttachSrc);
2977 blob_append_char(b, '}');
2978 }
2979 CARD_STR2(B, p->zBaseline);
2980 CARD_STR2(C, p->zComment);
2981 CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
2982 ISA( CFTYPE_EVENT ){
2983 blob_appendf(b, ", \"E\": {\"time\": %f, \"id\": %!j}",
2984 p->rEventDate, p->zEventId);
2985 }
2986 ISA( CFTYPE_MANIFEST ){
2987 CARD_LETTER(F);
2988 blob_append_char(b, '[');
2989 for( i = 0; i < p->nFile; ++i ){
2990 ManifestFile const * const pF = &p->aFile[i];
2991 if( i>0 ) blob_append_char(b, ',');
2992 blob_append_char(b, '{');
2993 KVP_STR(0, name, pF->zName);
2994 KVP_STR(1, uuid, pF->zUuid);
2995 KVP_STR(1, perm, pF->zPerm);
2996 KVP_STR(1, oldName, pF->zPrior);
2997 blob_append_char(b, '}');
2998 }
2999 /* Special case: model checkins with no F-card as having an empty
3000 ** array, rather than no F-cards, to hypothetically simplify
3001 ** handling in JSON queries. */
3002 blob_append_char(b, ']');
3003 }
3004 CARD_STR2(G, p->zThreadRoot);
3005 CARD_STR2(H, p->zThreadTitle);
3006 CARD_STR2(I, p->zInReplyTo);
3007 if( p->nField ){
3008 CARD_LETTER(J);
3009 blob_append_char(b, '[');
3010 for( i = 0; i < p->nField; ++i ){
3011 if( i>0 ) blob_append_char(b, ',');
3012 blob_append_char(b, '{');
3013 KVP_STR(0, name, p->aField[i].zName);
3014 KVP_STR(1, value, p->aField[i].zValue);
3015 blob_append_char(b, '}');
3016 }
3017 blob_append_char(b, ']');
3018 }
3019 CARD_STR2(K, p->zTicketUuid);
3020 CARD_STR2(L, p->zWikiTitle);
3021 ISA( CFTYPE_CLUSTER && p->nCChild>0 ){
3022 CARD_LETTER(M);
3023 blob_append_char(b, '[');
3024 for( int i = 0; i < p->nCChild; ++i ){
3025 if( i>0 ) blob_append_char(b, ',');
3026 blob_appendf(b, "%!j", p->azCChild[i]);
3027 }
3028 blob_append_char(b, ']');
3029 }
3030 CARD_STR2(N, p->zMimetype);
3031 ISA( CFTYPE_MANIFEST ){
3032 CARD_LETTER(P);
3033 blob_append_char(b, '[');
3034 if( p->nParent ){
3035 for( i = 0; i < p->nParent; ++i ){
3036 if( i>0 ) blob_append_char(b, ',');
3037 blob_appendf(b, "%!j", p->azParent[i]);
3038 }
3039 }
3040 /* Special case: model checkins with no P-card as having
3041 ** an empty array, rather than no P-card, to hypothetically
3042 ** simplify handling in JSON queries. */
3043 blob_append_char(b, ']');
3044 }
3045 if( p->nCherrypick ){
3046 CARD_LETTER(Q);
3047 blob_append_char(b, '[');
3048 for( i = 0; i < p->nCherrypick; ++i ){
3049 if( i>0 ) blob_append_char(b, ',');
3050 blob_append_char(b, '{');
3051 blob_appendf(b, "\"type\": \"%c\"", p->aCherrypick[i].zCPTarget[0]);
3052 KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
3053 KVP_STR(1, base, p->aCherrypick[i].zCPBase);
3054 blob_append_char(b, '}');
3055 }
3056 blob_append_char(b, ']');
3057 }
3058 CARD_STR2(R, p->zRepoCksum);
3059 if( p->nTag ){
3060 CARD_LETTER(T);
3061 blob_append_char(b, '[');
3062 for( int i = 0; i < p->nTag; ++i ){
3063 const char *zName = p->aTag[i].zName;
3064 if( i>0 ) blob_append_char(b, ',');
3065 blob_append_char(b, '{');
3066 blob_appendf(b, "\"type\": \"%c\"", *zName);
3067 ++zName;
3068 KVP_STR(1, name, zName);
3069 KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
3070 /* We could arguably resolve this to null. Per /chat
3071 discussion we don't want to resolve it to zUuid because
3072 that would effectively add information to the rendered
3073 manifest. */;
3074 KVP_STR(1, value, p->aTag[i].zValue);
3075 blob_append_char(b, '}');
3076 }
3077 blob_append_char(b, ']');
3078 }
3079 CARD_STR2(U, p->zUser);
3080 CARD_STR2(W, p->zWiki);
3081 fossil_free(zUuid);
3082 blob_append_literal(b, "}");
3083 #undef CARD_FMT
3084 #undef CARD_LETTER
3085 #undef CARD_STR
3086 #undef CARD_STR2
3087 #undef ISA
3088 #undef KVP_STR
3089 #undef STR_OR_NULL
3090 }
3091
3092 /*
3093 ** Convenience wrapper around artifact_to_json() which expects rid to
3094 ** be the blob.rid of any artifact type. If it can load a Manifest
3095 ** with that rid, it returns rid, else it returns 0.
3096 */
3097 int artifact_to_json_by_rid(int rid, Blob *pOut){
3098 Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
3099 if( p ){
3100 artifact_to_json(p, pOut);
3101 manifest_destroy(p);
3102 }else{
3103 rid = 0;
3104 }
3105 return rid;
3106 }
3107
3108 /*
3109 ** Convenience wrapper around artifact_to_json() which accepts any
3110 ** artifact name which is legal for symbolic_name_to_rid(). On success
3111 ** it returns the rid of the artifact. Returns 0 if no such artifact
3112 ** exists and a negative value if the name is ambiguous.
3113 **
3114 ** pOut is not cleared before rendering, so the caller needs to do
3115 ** that if it's important for their use case.
3116 */
3117 int artifact_to_json_by_name(const char *zName, Blob *pOut){
3118 const int rid = symbolic_name_to_rid(zName, 0);
3119 return rid>0
3120 ? artifact_to_json_by_rid(rid, pOut)
3121 : rid;
3122 }
3123
3124 /*
3125 ** SQLite UDF for artifact_to_json(). Its single argument should be
3126 ** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
3127 ** name, as per symbolic_name_to_rid(). If an artifact is found then
3128 ** the result of the UDF is that JSON as a string, else it evaluates
3129 ** to NULL.
3130 */
3131 void artifact_to_json_sql_func(
3132 sqlite3_context *context,
3133 int argc,
3134 sqlite3_value **argv
3135 ){
3136 int rid = 0;
3137 Blob b = empty_blob;
3138
3139 if(1 != argc){
3140 goto error_usage;
3141 }
3142 switch( sqlite3_value_type(argv[0]) ){
3143 case SQLITE_INTEGER:
3144 rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
3145 break;
3146 case SQLITE_TEXT:{
3147 const char * z = (const char *)sqlite3_value_text(argv[0]);
3148 if( z ){
3149 rid = artifact_to_json_by_name(z, &b);
3150 }
3151 break;
3152 }
3153 default:
3154 goto error_usage;
3155 }
3156 if( rid>0 ){
3157 sqlite3_result_text(context, blob_str(&b), blob_size(&b),
3158 SQLITE_TRANSIENT);
3159 blob_reset(&b);
3160 }else{
3161 /* We should arguably error out if rid<0 (ambiguous name) */
3162 sqlite3_result_null(context);
3163 }
3164 return;
3165 error_usage:
3166 sqlite3_result_error(context, "Expecting one argument: blob.rid or "
3167 "artifact symbolic name", -1);
3168 }
3169
3170
3171
3172 /*
3173 ** COMMAND: test-artifact-to-json
3174 **
3175 ** Usage: %fossil test-artifact-to-json ?-pretty? symbolic-name [...names]
3176 **
3177 ** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
3178 */
3179 void test_manifest_to_json(void){
3180 int i;
3181 Blob b = empty_blob;
3182 Stmt q;
3183 const int bPretty = find_option("pretty",0,0)!=0;
3184 int nErr = 0;
3185
3186 db_find_and_open_repository(0,0);
3187 db_prepare(&q, "select json_pretty(:json)");
3188 for( i=2; i<g.argc; ++i ){
3189 char const *zName = g.argv[i];
3190 const int rc = artifact_to_json_by_name(zName, &b);
3191 if( rc<=0 ){
3192 ++nErr;
3193 fossil_warning("Error reading artifact %Q", zName);
3194 continue;
3195 }else if( bPretty ){
3196 db_bind_blob(&q, ":json", &b);
3197 b.nUsed = 0;
3198 db_step(&q);
3199 db_column_blob(&q, 0, &b);
3200 db_reset(&q);
3201 }
3202 fossil_print("%b\n", &b);
3203 blob_reset(&b);
3204 }
3205 db_finalize(&q);
3206 if( nErr ){
3207 fossil_warning("Error count: %d", nErr);
3208 }
3209 }
3210

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button