@@ -3,6 +3,9 @@ use std::{collections::HashSet, fmt, hash::Hash};
33use itertools:: Itertools ;
44use rmpv:: { Utf8StringRef , Value , ValueRef } ;
55
6+ #[ cfg( test) ]
7+ use crate :: bridge:: nvim_dict;
8+
69#[ derive( Debug , Clone ) ]
710pub struct ApiInfoParseError ( String ) ;
811
@@ -36,21 +39,27 @@ pub struct ApiVersion {
3639 pub api_level : u64 ,
3740 pub api_compatible : u64 ,
3841 pub api_prerelease : bool ,
42+ pub prerelease_version : u64 ,
43+ pub prerelease_commit : String ,
44+ pub string : String ,
3945}
4046
4147impl ApiVersion {
48+ /// A pre-release only matches lower versions when prerelease is set to None.
49+ /// So NVIM v0.12.0-dev-1253+gfb2d736481 matches for the arguments 0, 11, 1, None but not for 0, 12, 0, None.
50+ /// When the prerelease is set to some number it compares the field after -dev in the version string.
51+ /// So the above version matches for the arguments 0, 12, 0, Some(1253), but not for 0, 12, 0, Some(1254).
52+ /// See the tests for more examples.
4253 #[ allow( dead_code) ]
43- pub fn has_version ( & self , major : u64 , minor : u64 , patch : u64 ) -> bool {
44- let actual_major = self . major ;
45- let actual_minor = self . minor ;
46- let actual_patch = self . patch ;
47- log:: trace!( "actual nvim version: {actual_major}.{actual_minor}.{actual_patch}" ) ;
48- log:: trace!( "expect nvim version: {major}.{minor}.{patch}" ) ;
49- let ret = actual_major > major
50- || ( actual_major == major && actual_minor > minor)
51- || ( actual_major == major && actual_minor == minor && actual_patch >= patch) ;
52- log:: trace!( "has desired nvim version: {ret}" ) ;
53- ret
54+ pub fn has_version ( & self , major : u64 , minor : u64 , patch : u64 , prerelease : Option < u64 > ) -> bool {
55+ self . major > major
56+ || ( self . major == major && self . minor > minor)
57+ || ( ( self . major == major && self . minor == minor && self . patch >= patch)
58+ && !self . prerelease )
59+ || ( self . major == major
60+ && self . minor == minor
61+ && self . patch == patch
62+ && matches ! ( prerelease, Some ( prerelease) if self . prerelease_version >= prerelease) )
5463 }
5564}
5665
@@ -184,7 +193,10 @@ impl ApiInformation {
184193 }
185194}
186195
187- fn parse_version ( value : ValueRef ) -> std:: result:: Result < ApiVersion , ApiInfoParseError > {
196+ fn parse_version (
197+ value : ValueRef ,
198+ version_str : & str ,
199+ ) -> std:: result:: Result < ApiVersion , ApiInfoParseError > {
188200 let mut major = None ;
189201 let mut minor = None ;
190202 let mut patch = None ;
@@ -209,6 +221,20 @@ fn parse_version(value: ValueRef) -> std::result::Result<ApiVersion, ApiInfoPars
209221 }
210222 }
211223
224+ // The api information does unfortunately not contain the detailed git information, so parse that from the version string
225+ // A pre-release has the following format
226+ // NVIM v0.12.0-dev-1253+gfb2d736481
227+ let mut prerelease_version = 0 ;
228+ let mut prerelease_commit = String :: default ( ) ;
229+ if let Some ( ( _, dev, version) ) = version_str. split ( '-' ) . collect_tuple ( ) {
230+ if dev == "dev" {
231+ if let Some ( ( version, commit) ) = version. split ( "+" ) . collect_tuple ( ) {
232+ prerelease_version = version. parse ( ) . unwrap_or ( 0 ) ;
233+ prerelease_commit = commit. to_string ( ) ;
234+ }
235+ }
236+ }
237+
212238 Ok ( ApiVersion {
213239 major : major. ok_or ( "major field is missing" ) ?,
214240 minor : minor. ok_or ( "minor field is missing" ) ?,
@@ -217,6 +243,9 @@ fn parse_version(value: ValueRef) -> std::result::Result<ApiVersion, ApiInfoPars
217243 api_level : api_level. ok_or ( "api_level field is missing" ) ?,
218244 api_compatible : api_compatible. ok_or ( "api_compatible field is missing" ) ?,
219245 api_prerelease : api_prerelase. ok_or ( "api_prerelease field is missing" ) ?,
246+ prerelease_version,
247+ prerelease_commit,
248+ string : version_str. to_string ( ) ,
220249 } )
221250}
222251
@@ -343,7 +372,10 @@ fn parse_ui_events(value: ValueRef) -> std::result::Result<HashSet<ApiEvent>, Ap
343372 . collect :: < std:: result:: Result < HashSet < _ > , _ > > ( )
344373}
345374
346- pub fn parse_api_info ( value : & [ Value ] ) -> std:: result:: Result < ApiInformation , ApiInfoParseError > {
375+ pub fn parse_api_info (
376+ value : & [ Value ] ,
377+ version_str : & str ,
378+ ) -> std:: result:: Result < ApiInformation , ApiInfoParseError > {
347379 let channel = value[ 0 ] . as_ref ( ) . try_into ( ) ?;
348380
349381 let metadata: Vec < ( ValueRef , ValueRef ) > = value[ 1 ] . as_ref ( ) . try_into ( ) ?;
@@ -356,7 +388,7 @@ pub fn parse_api_info(value: &[Value]) -> std::result::Result<ApiInformation, Ap
356388 for ( k, v) in metadata {
357389 let k: Utf8StringRef = k. try_into ( ) ?;
358390 match k. as_str ( ) {
359- Some ( "version" ) => version = Some ( parse_version ( v) ?) ,
391+ Some ( "version" ) => version = Some ( parse_version ( v, version_str ) ?) ,
360392 Some ( "functions" ) => functions = Some ( parse_functions ( v) ?) ,
361393 Some ( "ui_options" ) => ui_options = Some ( parse_string_vec ( v) ?) ,
362394 Some ( "ui_events" ) => ui_events = Some ( parse_ui_events ( v) ?) ,
@@ -372,3 +404,67 @@ pub fn parse_api_info(value: &[Value]) -> std::result::Result<ApiInformation, Ap
372404 ui_events : ui_events. ok_or ( "ui_events field is missing" ) ?,
373405 } )
374406}
407+
408+ #[ test]
409+ fn version_match ( ) {
410+ let value = nvim_dict ! {
411+ "major" => 1 ,
412+ "minor" => 11 ,
413+ "patch" => 4 ,
414+ "prerelease" => false ,
415+ "api_level" => 0 ,
416+ "api_compatible" => 0 ,
417+ "api_prerelease" => false ,
418+ } ;
419+
420+ let version = parse_version ( Value :: from ( value) . as_ref ( ) , "NVIM v1.11.4" ) . unwrap ( ) ;
421+ assert ! ( version. has_version( 1 , 11 , 4 , None ) ) ;
422+ assert ! ( version. has_version( 1 , 11 , 3 , None ) ) ;
423+ assert ! ( version. has_version( 1 , 10 , 0 , None ) ) ;
424+ assert ! ( version. has_version( 0 , 11 , 4 , None ) ) ;
425+ // We also have a pre-release of previous versions
426+ assert ! ( version. has_version( 1 , 11 , 4 , Some ( 1253 ) ) ) ;
427+ assert ! ( version. has_version( 1 , 10 , 4 , Some ( 1253 ) ) ) ;
428+ assert ! ( !version. has_version( 1 , 11 , 5 , None ) ) ;
429+ assert ! ( !version. has_version( 1 , 12 , 4 , None ) ) ;
430+ assert ! ( !version. has_version( 2 , 11 , 4 , None ) ) ;
431+ // We don't have a pre-release of newer versions
432+ assert ! ( !version. has_version( 1 , 12 , 0 , Some ( 0 ) ) ) ;
433+ assert ! ( !version. has_version( 1 , 11 , 5 , Some ( 0 ) ) ) ;
434+ }
435+
436+ #[ test]
437+ fn version_match_prerelease ( ) {
438+ let value = nvim_dict ! {
439+ "major" => 1 ,
440+ "minor" => 12 ,
441+ "patch" => 0 ,
442+ "prerelease" => true ,
443+ "api_level" => 0 ,
444+ "api_compatible" => 0 ,
445+ "api_prerelease" => false ,
446+ } ;
447+
448+ let version = parse_version (
449+ Value :: from ( value) . as_ref ( ) ,
450+ "NVIM v1.12.0-dev-1253+gfb2d736481" ,
451+ )
452+ . unwrap ( ) ;
453+ assert ! ( version. has_version( 1 , 11 , 4 , None ) ) ;
454+ assert ! ( version. has_version( 1 , 11 , 3 , None ) ) ;
455+ assert ! ( version. has_version( 1 , 10 , 0 , None ) ) ;
456+ assert ! ( version. has_version( 0 , 11 , 4 , None ) ) ;
457+ // We also have a pre-release of previous versions
458+ assert ! ( version. has_version( 1 , 11 , 4 , Some ( 1253 ) ) ) ;
459+ assert ! ( version. has_version( 1 , 10 , 4 , Some ( 1253 ) ) ) ;
460+ assert ! ( version. has_version( 1 , 11 , 5 , None ) ) ;
461+ assert ! ( !version. has_version( 1 , 12 , 4 , None ) ) ;
462+ assert ! ( !version. has_version( 2 , 11 , 4 , None ) ) ;
463+ // We have a pre-release that allows these
464+ assert ! ( version. has_version( 1 , 12 , 0 , Some ( 0 ) ) ) ;
465+ assert ! ( version. has_version( 1 , 12 , 0 , Some ( 1253 ) ) ) ;
466+ assert ! ( version. has_version( 1 , 11 , 5 , Some ( 0 ) ) ) ;
467+ // But not these
468+ assert ! ( !version. has_version( 1 , 12 , 1 , Some ( 0 ) ) ) ;
469+ assert ! ( !version. has_version( 1 , 12 , 0 , Some ( 1254 ) ) ) ;
470+ }
0 commit comments