123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532openCore_kernelincludeAscii_table_kernel_intfletlist_sum~flst=List.foldlst~init:0~f:(funab->a+fb)letlist_max~flst=List.foldlst~init:0~f:(funab->maxa(fb))moduleEl=struct(* One element in the table. *)typet=Attr.tlist*stringlisttyperow=tlisttypegrid=rowlistletcreateattrstr=attr,String.split_linesstrletwidth(_,lines)=list_max~f:String.lengthlinesletheightwidth(_,lines)~display_empty_rows=letheight=list_sumlines~f:(funs->max((String.lengths+(width-1))/maxwidth1)1)inifdisplay_empty_rowsthenmaxheight1elseheight;;letrecsliceswidthlines=matchlineswith|[]->[]|line::lines->slices_splitwidthlinesline(String.lengthline)0andslices_splitwidthlineslineline_lenpos=letchunk_len=minwidth(line_len-pos)inletcompletely_fits=Int.(=)chunk_len(line_len-pos)inletchunk=String.subline~pos~len:chunk_leninifcompletely_fitsthenchunk::sliceswidthlineselsechunk::slices_splitwidthlineslineline_len(pos+width);;endtypeshow=[`Yes|`No|`If_not_empty]moduleColumn=structtype'at={max_width:int;header:string;col_func:'a->El.t;align:Align.t;min_width:intoption;show:show}letheadert=t.headerletto_datat=t.col_functypeconstraints={total_width:int;min_widths:(string*int)list}[@@derivingsexp]exceptionImpossible_table_constraintsofconstraints[@@derivingsexp]letcreate_attr?(align=Align.Left)?min_width?(max_width=90)?(show=`Yes)strparse_func={max_width;header=str;col_func=(funx->matchparse_funcxwith|a,b->El.createab);align;(* We add one for the '|' on the left. *)min_width=Option.mapmin_width~f:((+)1);show};;letcreate?(align=Align.Left)?min_width?(max_width=90)?showstrparse_func=create_attr?min_width~align~max_width?showstr(funx->[],parse_funcx);;letheader_to_elalstt=El.createalstt.headerletmakecol_valt=t.col_funccol_valletdesired_width~spacingdatat=letcolumn_data=List.mapdata~f:t.col_funcinletheader_width=String.splitt.header~on:'\n'|>list_max~f:String.lengthin(* We need to account for the '|' to the left, so we add 1 plus the spacing
on either side. *)1+(2*spacing)+min(t.max_width-(2*spacing))(maxheader_width(list_maxcolumn_data~f:El.width));;letlayout~spacingtable_widthtsdata=letdesired_widths=List.mapts~f:(desired_width~spacingdata)inletall_min_width=List.filter_mapts~f:(funt->t.min_width)in(* [generic_min_chars] = minimum number of characters for a column that doesn't have
an [min_width] value. *)lettable_constraints_are_impossible,generic_min_chars=letcolumns_with_no_min_width=List.lengthts-List.lengthall_min_widthinifInt.(<>)0columns_with_no_min_width(* need to avoid a divide-by-zero *)then(letwidth=table_width-list_sumall_min_width~f:Fn.idinletgeneric_min_chars=width/columns_with_no_min_widthinletimpossible=generic_min_chars<1+(1+(spacing*2))inimpossible,generic_min_chars)else(letmin_total=List.fold~init:0all_min_width~f:Int.(+)inletextra_per_col=1+1+(spacing*2)inletimpossible=table_width<min_total+(List.lengthts*extra_per_col)in(* the zero is a nonsense value, but we only generate it when every column has a
min width and therefore this zero will never be used. *)impossible,0)iniftable_constraints_are_impossiblethenraise(Impossible_table_constraints{total_width=table_width+1;min_widths=List.filter_mapts~f:(funt->Option.mapt.min_width~f:(funnum_chars->t.header,num_chars))});letleft=ref(list_sum~f:Fn.iddesired_widths-table_width)inletstop=reffalsein(* This layout algorithm looks unbearably inefficient, but it's
simple and works reasonably well in the common case. *)letrecdecide_widthsdesired_widths=if!stopthendesired_widthselse(stop:=true;assert(List.lengthts=List.lengthdesired_widths);decide_widths(List.map2_exntsdesired_widths~f:(funtcolumn_width->letmin_chars=matcht.min_widthwith|Somex->x|None->generic_min_charsinifcolumn_width<=min_chars||!left<=0thencolumn_widthelse(left:=!left-1;stop:=false;column_width-1))))in(* The widths used in [loop] include the '|' to the left of each element,
which isn't important after layout, so we subtract off 1 and the spacing
on either side. *)List.map~f:(funx->x-(1+(spacing*2)))(decide_widthsdesired_widths);;moduleOf_field=structletfield?align?min_width?max_width?show?headerto_stringrecord_field=create?align?min_width?max_width?show(Option.valueheader~default:(Field.namerecord_field))(funrecord->to_string(Field.getrecord_fieldrecord));;letfield_attr?align?min_width?max_width?show?headerto_string_and_attrrecord_field=create_attr?align?min_width?max_width?show(Option.valueheader~default:(Field.namerecord_field))(funrecord->to_string_and_attr(Field.getrecord_fieldrecord));;letfield_opt?align?min_width?max_width?show?headerto_stringrecord_field=field?align?min_width?max_width?show?header(function|None->""|Somex->to_stringx)record_field;;letfield_opt_attr?align?min_width?max_width?show?headerto_string_and_attrrecord_field=field_attr?align?min_width?max_width?show?header(function|None->[],""|Somex->to_string_and_attrx)record_field;;endendmoduleTable_char=structtypet={ascii:char;utf8:string}letconnect?top?bottom?left?right()=lettop,bottom,left,right=is_sometop,is_somebottom,is_someleft,is_somerightinletascii,utf8=matchtop,bottom,left,rightwith|false,false,true,true->'-',"\226\148\128"|true,true,false,false->'|',"\226\148\130"|false,true,false,true->'|',"\226\148\140"|false,true,true,false->'|',"\226\148\144"|true,false,false,true->'|',"\226\148\148"|true,false,true,false->'|',"\226\148\152"|true,true,false,true->'|',"\226\148\156"|true,true,true,false->'|',"\226\148\164"|false,true,true,true->'-',"\226\148\172"|true,false,true,true->'-',"\226\148\180"|true,true,true,true->'+',"\226\148\188"|false,false,true,false->'-',"\226\149\180"|true,false,false,false->'|',"\226\149\181"|false,false,false,true->'-',"\226\149\182"|false,true,false,false->'|',"\226\149\183"|false,false,false,false->' '," "in{ascii;utf8};;endmoduleScreen=structtypepoint=|Line|Blank|CharofAttr.tlist*char[@@derivingcompare,sexp_of]typet={data:pointarrayarray;rows:int;cols:int}[@@derivingcompare,sexp_of]letcreate~rows~cols=letdata=Array.make_matrix~dimx:rows~dimy:cols(Char([],' '))in{data;rows;cols};;letset_screen_pointt~row~col~point=letc=t.data.(row).(col)inletnew_point=matchc,pointwith|Blank,_->Blank|_,Blank->Blank|_,point->pointint.data.(row).(col)<-new_point;;lethlinet~row~col1~col2?(point=Line)()=forcol=col1tocol2doset_screen_pointt~row~col~pointdone;;letvlinet~col~row1~row2?(point=Line)()=forrow=row1torow2doset_screen_pointt~row~col~pointdone;;letchart~row~col~char~attr=t.data.(row).(col)<-Char(attr,char)letstringt~row~col~string~attr=fori=0toString.lengthstring-1dochart~row~col:(col+i)~char:string.[i]~attrdone;;letalignedt~row~col~string:str~attr~width~align=letcol=matchalignwith|Align.Left->col|Align.Right->col+width-String.lengthstr|Align.Center->col+(max0(width-String.lengthstr)/2)instringt~row~col~string:str~attr;;letget_symbolt~row~col=lettop=row>0&&[%compare.equal:point]t.data.(row-1).(col)Lineinletbottom=row<t.rows-1&&[%compare.equal:point]t.data.(row+1).(col)Lineinletleft=col>0&&[%compare.equal:point]t.data.(row).(col-1)Lineinletright=col<t.cols-1&&[%compare.equal:point]t.data.(row).(col+1)LineinTable_char.connect?top:(Option.some_iftop())?bottom:(Option.some_ifbottom())?left:(Option.some_ifleft())?right:(Option.some_ifright())();;letrendert~bars~output~close=letbuf=Buffer.create1024inletcurrent_attr=ref[]inletupdate_attrattr=letattr=List.sort~compare:[%compare:Attr.t]attrinifnot([%compare.equal:Attr.tlist]attr!current_attr)then(ifBuffer.lengthbuf>0thenoutput!current_attrbuf;current_attr:=attr)inforrow=0tot.rows-1doforcol=0tot.cols-1domatcht.data.(row).(col)with|Char(attr,ch)->update_attrattr;Buffer.add_charbufch|Blank->Buffer.add_charbuf' '|Line->update_attr[];let{Table_char.ascii;utf8}=get_symbolt~row~colin(matchbarswith|`Ascii->Buffer.add_charbufascii|`Unicode->Buffer.add_stringbufutf8)done;update_attr[];Buffer.add_charbuf'\n'done;output!current_attrbuf;closebuf;;letto_stringt~bars~string_with_attr=letbuf=Buffer.create1024inrendert~bars~output:(funattrbuf'->Buffer.add_stringbuf(string_with_attrattr(Buffer.contentsbuf'));Buffer.clearbuf')~close:(fun_->Buffer.contentsbuf);;endmoduleDisplay=structtypet=|Short_box|Tall_box|Line|Blank|Column_titles[@@derivingcompare,sexp_of]letshort_box=Short_boxlettall_box=Tall_boxletline=Lineletblank=Blankletcolumn_titles=Column_titlesendmoduleGrid=structtypet={data:El.grid;heights:intlist;widths:intlist;aligns:Align.tlist}letcreate~spacing~displaymax_widthh_attrcolsraw_data~display_empty_rows=letbody=List.mapraw_data~f:(funx->List.mapcols~f:(Column.makex))inletempty=List.foldbody~init:(List.mapcols~f:(fun_->true))~f:(List.map2_exn~f:(funis_empty(_attr,lines)->is_empty&&List.for_alllines~f:(String.equal"")))inletkeep=List.map2_exncolsempty~f:(fun{Column.show;_}is_empty->matchshowwith|`Yes->true|`No->false|`If_not_empty->notis_empty)inletfilterl=List.filter_opt(List.map2_exnkeepl~f:Option.some_if)inletcols=filtercolsinletbody=List.mapbody~f:filterin(* We subtract 1 from max_width because later we're going to add a line of
'|'s to form the right wall of the table. *)letwidths=Column.layout~spacing(max_width-1)colsraw_datainletgrid_data=List.mapcols~f:(Column.header_to_elh_attr)::bodyinletheights=if[%compare.equal:Display.t]displayLinethenList.mapgrid_data~f:(fun_->1)elseList.mapgrid_data~f:(funrow->assert(List.lengthwidths=List.lengthrow);list_max~f:Fn.id(List.map2_exnwidthsrow~f:(El.height~display_empty_rows)))inletaligns=List.mapcols~f:(func->c.Column.align)in{data=grid_data;heights;widths;aligns};;letdraw~spacing~displayt=assert(List.lengtht.data=List.lengtht.heights);letmid_row=if[%compare.equal:Display.t]displayTall_boxthen1else0in(* The total width of the table includes the '|'s to the left of elements, so we add 1
and the spacing on either side when summing. *)letcols=list_sumt.widths~f:((+)(1+(spacing*2)))+1inletrows=list_sumt.heights~f:((+)mid_row)+3-(2*mid_row)inletscreen=Screen.create~rows~colsinletpoint=if[%compare.equal:Display.t]displayColumn_titlesthenScreen.BlankelseScreen.LineinScreen.hlinescreen~row:0~col1:0~col2:(cols-1)~point();Screen.hlinescreen~row:(rows-1)~col1:0~col2:(cols-1)~point();ifnot([%compare.equal:Display.t]displayBlank)then(Screen.vlinescreen~col:0~row1:0~row2:(rows-1)~point();ignore(List.foldt.widths~init:0~f:(funcolwidth->letcol=col+1+width+(spacing*2)inScreen.vlinescreen~col~row1:0~row2:(rows-1)~point();col):int));ignore(List.fold2_exnt.datat.heights~init:1~f:(funrowrow_elementsheight->letheader_row=row=1inignore(List.fold2_exnrow_elements(List.zip_exnt.widthst.aligns)~init:(1+spacing)~f:(funcol(attr,lines)(width,align)->letstrings=El.sliceswidthlinesinif[%compare.equal:Display.t]displayLinethen(matchstringswith|[]->()|[string]->Screen.alignedscreen~row~col~attr~string~align~width|string::_->Screen.alignedscreen~row~col~attr~string~align~width;forcol=col+max0(width-3)tocol+width-1doScreen.charscreen~row~col~char:'.'~attr:[]done)elseignore(List.foldstrings~init:row~f:(funrowstring->Screen.alignedscreen~row~col~attr~string~align~width;row+1):int);col+1+(spacing*2)+width):int);letrow=row+heightinif[%compare.equal:Display.t]displayTall_box||header_rowthen(ifnot([%compare.equal:Display.t]displayBlank)thenScreen.hlinescreen~row~col1:0~col2:(cols-1)();row+1)elserow):int);screen;;endletdraw?(display=Display.short_box)?(spacing=1)?(limit_width_to=90)?(header_attr=[])?(display_empty_rows=false)colsdata=matchcolswith|[]->None|_::_->Some(Grid.create~spacing~displaylimit_width_toheader_attrcolsdata~display_empty_rows|>Grid.draw~spacing~display);;