1 // Copyright 2010 Wincent Colaiuta. All rights reserved.
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
6 // 1. Redistributions of source code must retain the above copyright notice,
7 // this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright notice,
9 // this list of conditions and the following disclaimer in the documentation
10 // and/or other materials provided with the distribution.
12 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
13 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
16 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22 // POSSIBILITY OF SUCH DAMAGE.
26 #include "ruby_compat.h"
28 // use a struct to make passing params during recursion easier
31 char *str_p
; // pointer to string to be searched
32 long str_len
; // length of same
33 char *abbrev_p
; // pointer to search string (abbreviation)
34 long abbrev_len
; // length of same
35 double max_score_per_char
;
36 int dot_file
; // boolean: true if str is a dot-file
37 int always_show_dot_files
; // boolean
38 int never_show_dot_files
; // boolean
41 double recursive_match(matchinfo_t
*m
, // sharable meta-data
42 long str_idx
, // where in the path string to start
43 long abbrev_idx
, // where in the search string to start
44 long last_idx
, // location of last matched character
45 double score
) // cumulative score so far
47 double seen_score
= 0; // remember best score seen via recursion
48 int dot_file_match
= 0; // true if abbrev matches a dot-file
49 int dot_search
= 0; // true if searching for a dot
51 for (long i
= abbrev_idx
; i
< m
->abbrev_len
; i
++)
53 char c
= m
->abbrev_p
[i
];
57 for (long j
= str_idx
; j
< m
->str_len
; j
++, str_idx
++)
62 if (j
== 0 || m
->str_p
[j
- 1] == '/')
64 m
->dot_file
= 1; // this is a dot-file
65 if (dot_search
) // and we are searching for a dot
66 dot_file_match
= 1; // so this must be a match
69 else if (d
>= 'A' && d
<= 'Z')
70 d
+= 'a' - 'A'; // add 32 to downcase
77 double score_for_char
= m
->max_score_per_char
;
78 long distance
= j
- last_idx
;
82 char last
= m
->str_p
[j
- 1];
83 char curr
= m
->str_p
[j
]; // case matters, so get again
86 else if (last
== '-' ||
89 (last
>= '0' && last
<= '9'))
91 else if (last
>= 'a' && last
<= 'z' &&
92 curr
>= 'A' && curr
<= 'Z')
97 // if no "special" chars behind char, factor diminishes
98 // as distance from last matched char increases
99 factor
= (1.0 / distance
) * 0.75;
100 score_for_char
*= factor
;
103 if (++j
< m
->str_len
)
105 // bump cursor one char to the right and
106 // use recursion to try and find a better match
107 double sub_score
= recursive_match(m
, j
, i
, last_idx
, score
);
108 if (sub_score
> seen_score
)
109 seen_score
= sub_score
;
112 score
+= score_for_char
;
113 last_idx
= str_idx
++;
122 if (m
->never_show_dot_files
||
123 (!dot_file_match
&& !m
->always_show_dot_files
))
126 return (score
> seen_score
) ? score
: seen_score
;
129 // Match.new abbrev, string, options = {}
130 VALUE
CommandTMatch_initialize(int argc
, VALUE
*argv
, VALUE self
)
132 // process arguments: 2 mandatory, 1 optional
133 VALUE str
, abbrev
, options
;
134 if (rb_scan_args(argc
, argv
, "21", &str
, &abbrev
, &options
) == 2)
136 str
= StringValue(str
);
137 abbrev
= StringValue(abbrev
); // already downcased by caller
139 // check optional options hash for overrides
140 VALUE always_show_dot_files
= CommandT_option_from_hash("always_show_dot_files", options
);
141 VALUE never_show_dot_files
= CommandT_option_from_hash("never_show_dot_files", options
);
144 m
.str_p
= RSTRING_PTR(str
);
145 m
.str_len
= RSTRING_LEN(str
);
146 m
.abbrev_p
= RSTRING_PTR(abbrev
);
147 m
.abbrev_len
= RSTRING_LEN(abbrev
);
148 m
.max_score_per_char
= (1.0 / m
.str_len
+ 1.0 / m
.abbrev_len
) / 2;
150 m
.always_show_dot_files
= always_show_dot_files
== Qtrue
;
151 m
.never_show_dot_files
= never_show_dot_files
== Qtrue
;
155 if (m
.abbrev_len
== 0) // special case for zero-length search string
157 // filter out dot files
158 if (!m
.always_show_dot_files
)
160 for (long i
= 0; i
< m
.str_len
; i
++)
163 if (c
== '.' && (i
== 0 || m
.str_p
[i
- 1] == '/'))
172 score
= recursive_match(&m
, 0, 0, 0, 0.0);
174 // clean-up and final book-keeping
175 rb_iv_set(self
, "@score", rb_float_new(score
));
176 rb_iv_set(self
, "@str", str
);
180 VALUE
CommandTMatch_matches(VALUE self
)
182 double score
= NUM2DBL(rb_iv_get(self
, "@score"));
183 return score
> 0 ? Qtrue
: Qfalse
;
186 VALUE
CommandTMatch_to_s(VALUE self
)
188 return rb_iv_get(self
, "@str");