123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219(******************************************************************************)(* This file is part of the Dose library http://www.irill.org/software/dose *)(* *)(* Copyright (C) 2011 Ralf Treinen <ralf.treinen@pps.jussieu.fr> *)(* *)(* This library is free software: you can redistribute it and/or modify *)(* it under the terms of the GNU Lesser General Public License as *)(* published by the Free Software Foundation, either version 3 of the *)(* License, or (at your option) any later version. A special linking *)(* exception to the GNU Lesser General Public License applies to this *)(* library, see the COPYING file for more information. *)(* *)(* Work developed with the support of the Mancoosi Project *)(* http://www.mancoosi.org *)(* *)(******************************************************************************)openDose_commonmodulePcre=Re_pcreincludeUtil.Logging(structletlabel="dose_versioning.debian"end)letis_digit=function'0'..'9'->true|_->false(* [skip_while_from i f w m] yields the index of the leftmost character
* in the string [s], starting from [i], end ending at [m], that does
* not satisfy the predicate [f], or [length w] if no such index exists. *)letskip_while_fromifwm=letrecloopi=ifi=mthenielseiffw.[i]thenloop(i+1)elseiinloopi(* splits a version into (epoch,rest), without the separating ':'. The
* epoch is delimited by the leftmost occurrence of ':' in x, and is ""
* in case there is no ':' in x. *)letextract_epochx=tryletci=String.indexx':'inletepoch=String.subx0ciandrest=String.subx(ci+1)(String.lengthx-ci-1)in(epoch,rest)withNot_found->("",x)(* splits a version into (prefix,revision). The revision starts on the
* right-most occurrence of '-', or is empty in case the version does
* not contain '-'. *)letextract_revisionx=tryletdi=String.rindexx'-'inletbefore=String.subx0diinletafter=String.subx(di+1)(String.lengthx-di-1)in(before,after)withNot_found->(x,"")(* character comparison uses a modified character ordering: '~' first,
then letters, then anything else *)letcompare_charsc1c2=matchc1with|'~'->(matchc2with'~'->0|_->-1)|'a'..'z'|'A'..'Z'->(matchc2with|'~'->1|'a'..'z'|'A'..'Z'->Char.comparec1c2|_->-1)|_->(matchc2with|'~'|'a'..'z'|'A'..'Z'->1|_->Char.comparec1c2)(* return the first index of x, starting from xi, of a nun-null
* character in x. or (length x) in case x contains only 0's starting
* from xi on. *)letskip_zerosxxixl=skip_while_fromxi(func->c='0')xxl(* compare versions chunks, that is parts of version strings that are
* epoch, upstream version, or revisision. Alternates string comparison
* and numerical comaprison. *)letcompare_chunksxy=(* x and y may be empty *)letxl=String.lengthxandyl=String.lengthyinletrecloop_lexicalxiyi=assert(xi<=xl&&yi<=yl);match(xi=xl,yi=yl)with(* which of x and y is exhausted? *)|(true,true)->0|(true,false)->(* if y continues numerically than we have to continue by
* comparing numerically. In this case the x part is
* interpreted as 0 (since empty). If the y part consists
* only of 0's then both parts are equal, otherwise the y
* part is larger. If y continues non-numerically then y is
* larger anyway, so we only have to skip 0's in the y part
* and check whether this exhausts the y part. *)letys=skip_zerosyyiylinifys=ylthen0elseify.[ys]='~'then1else-1|(false,true)->(* symmetric to the preceding case *)letxs=skip_zerosxxixlinifxs=xlthen0elseifx.[xs]='~'then-1else1|(false,false)->((* which of x and y continues numerically? *)match(is_digitx.[xi],is_digity.[yi])with|(true,true)->(* both continue numerically. Skip leading zeros in the
* remaining parts, and then continue by
* comparing numerically. *)compare_numerical(skip_zerosxxixl)(skip_zerosyyiyl)|(true,false)->(* '~' is smaller than any numeric part *)ify.[yi]='~'then1else-1|(false,true)->(* '~' is smaller than any numeric part *)ifx.[xi]='~'then-1else1|(false,false)->(* continue comparing lexically *)letcomp=compare_charsx.[xi]y.[yi]inifcomp=0thenloop_lexical(xi+1)(yi+1)elsecomp)andcompare_numericalxiyi=assert(xi=xl||(xi<xl&&x.[xi]<>'0'));(* leading zeros have been stripped *)assert(yi=yl||(yi<yl&&y.[yi]<>'0'));(* leading zeros have been stripped *)letxn=skip_while_fromxiis_digitxxl(* length of numerical part *)andyn=skip_while_fromyiis_digityyl(* length of numerical part *)inletcomp=compare(xn-xi)(yn-yi)inifcomp=0then(* both numerical parts have same length: compare digit by digit *)loop_numericalxiyiynelse(* if one numerical part is longer than the other we have found the
* answer since leading 0 have been striped when switching
* to numerical comparison. *)compandloop_numericalxiyiyn=assert(xi<=xl&&yi<=yn&&yn<=yl);(* invariant: the two numerical parts that remain to compare are
of the same length *)ifyi=ynthen(* both numerical parts are exhausted, we switch to lexical
comparison *)loop_lexicalxiyielse(* both numerical parts are not exhausted, we continue comparing
digit by digit *)letcomp=Char.comparex.[xi]y.[yi]inifcomp=0thenloop_numerical(xi+1)(yi+1)ynelsecompinloop_lexical00letcompare(x:string)(y:string)=letnormalize_comp_resultx=ifx=0then0elseifx<0then-1else1inifx=ythen0elselet(e1,rest1)=extract_epochxand(e2,rest2)=extract_epochyinlete_comp=compare_chunkse1e2inife_comp<>0thennormalize_comp_resulte_compelselet(u1,r1)=extract_revisionrest1and(u2,r2)=extract_revisionrest2inletu_comp=compare_chunksu1u2inifu_comp<>0thennormalize_comp_resultu_compelsenormalize_comp_result(compare_chunksr1r2)letequal(x:string)(y:string)=ifx=ythentrueelsecomparexy=0(************** splitting and recomposing version strings *******************)typeversion_analysis=|Nativeofstring*string*string(* epoch,upstream,binnmu *)|NonNativeofstring*string*string*string(* epoch,upstream,revision,binnmu *)letbinnmu_regexp=Pcre.regexp"^(.*)\\+(b[\\d]+)$"letextract_binnmuv=tryletsubs=Pcre.extract~rex:binnmu_regexpvin(subs.(1),subs.(2))withNot_found->(v,"")letdecomposev=let(epoch,rest)=extract_epochvinlet(upstream_complete,revision_complete)=extract_revisionrestinifrevision_complete=""thenlet(upstream,binnmu)=extract_binnmuupstream_completeinNative(epoch,upstream,binnmu)elselet(revision,binnmu)=extract_binnmurevision_completeinNonNative(epoch,upstream_complete,revision,binnmu)letcompose=function|Native("",upstream,"")->upstream|Native(epoch,upstream,"")->epoch^":"^upstream|Native("",upstream,binnmu)->upstream^"+"^binnmu|Native(epoch,upstream,binnmu)->epoch^":"^upstream^"+"^binnmu|NonNative("",upstream,revision,"")->upstream^"-"^revision|NonNative(epoch,upstream,revision,"")->epoch^":"^upstream^"-"^revision|NonNative("",upstream,revision,binnmu)->upstream^"-"^revision^"+"^binnmu|NonNative(epoch,upstream,revision,binnmu)->epoch^":"^upstream^"-"^revision^"+"^binnmuletstrip_epoch_binnmuv=matchdecomposevwith|Native(_,upstream,_)->Native("",upstream,"")|NonNative(_,upstream,revision,_)->NonNative("",upstream,revision,"")letstrip_epochv=matchdecomposevwith|Native(_,upstream,binnmu)->Native("",upstream,binnmu)|NonNative(_,upstream,revision,binnmu)->NonNative("",upstream,revision,binnmu)letextract_epochv=fst(extract_epochv)