Skip to content

Commit be4e317

Browse files
Merge pull request RustPython#822 from jgirardet/addbytesm
add capitalize bytes.center + fix str.center
2 parents 78ab68a + 73dbcfb commit be4e317

File tree

5 files changed

+166
-4
lines changed

5 files changed

+166
-4
lines changed

tests/snippets/bytes.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,14 @@
109109
assert bytes(b"Is Title Case").istitle()
110110
assert not bytes(b"is Not title casE").istitle()
111111

112-
# upper lower
112+
# upper lower, capitalize
113113
l = bytes(b"lower")
114114
b = bytes(b"UPPER")
115115
assert l.lower().islower()
116116
assert b.upper().isupper()
117+
assert l.capitalize() == b"Lower"
118+
assert b.capitalize() == b"Upper"
119+
assert bytes().capitalize() == bytes()
117120

118121
# hex from hex
119122
assert bytes([0, 1, 9, 23, 90, 234]).hex() == "000109175aea"
@@ -127,3 +130,31 @@
127130
bytes.fromhex("6Z2")
128131
except ValueError as e:
129132
str(e) == "non-hexadecimal number found in fromhex() arg at position 1"
133+
134+
# center
135+
assert [b"koki".center(i, b"|") for i in range(3, 10)] == [
136+
b"koki",
137+
b"koki",
138+
b"|koki",
139+
b"|koki|",
140+
b"||koki|",
141+
b"||koki||",
142+
b"|||koki||",
143+
]
144+
145+
assert [b"kok".center(i, b"|") for i in range(2, 10)] == [
146+
b"kok",
147+
b"kok",
148+
b"kok|",
149+
b"|kok|",
150+
b"|kok||",
151+
b"||kok||",
152+
b"||kok|||",
153+
b"|||kok|||",
154+
]
155+
b"kok".center(4) == b" kok" # " test no arg"
156+
with assertRaises(TypeError):
157+
b"b".center(2, "a")
158+
with assertRaises(TypeError):
159+
b"b".center(2, b"ba")
160+
b"kok".center(5, bytearray(b"x"))

tests/snippets/strings.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@
5555
c = 'hallo'
5656
assert c.capitalize() == 'Hallo'
5757
assert c.center(11, '-') == '---hallo---'
58+
assert ["koki".center(i, "|") for i in range(3, 10)] == [
59+
"koki",
60+
"koki",
61+
"|koki",
62+
"|koki|",
63+
"||koki|",
64+
"||koki||",
65+
"|||koki||",
66+
]
67+
68+
69+
assert ["kok".center(i, "|") for i in range(2, 10)] == [
70+
"kok",
71+
"kok",
72+
"kok|",
73+
"|kok|",
74+
"|kok||",
75+
"||kok||",
76+
"||kok|||",
77+
"|||kok|||",
78+
]
79+
80+
5881
# assert c.isascii()
5982
assert c.index('a') == 1
6083
assert c.rindex('l') == 3

vm/src/obj/objbyteinner.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::pyobject::PyObjectRef;
2+
use num_bigint::BigInt;
23

34
use crate::function::OptionalArg;
45

@@ -13,8 +14,12 @@ use std::hash::{Hash, Hasher};
1314
use super::objint;
1415
use super::objsequence::PySliceableSequence;
1516
use crate::obj::objint::PyInt;
17+
use num_integer::Integer;
1618
use num_traits::ToPrimitive;
1719

20+
use super::objbytearray::{get_value as get_value_bytearray, PyByteArray};
21+
use super::objbytes::PyBytes;
22+
1823
#[derive(Debug, Default, Clone)]
1924
pub struct PyByteInner {
2025
pub elements: Vec<u8>,
@@ -305,6 +310,15 @@ impl PyByteInner {
305310
self.elements.to_ascii_uppercase()
306311
}
307312

313+
pub fn capitalize(&self, _vm: &VirtualMachine) -> Vec<u8> {
314+
let mut new: Vec<u8> = Vec::new();
315+
if let Some((first, second)) = self.elements.split_first() {
316+
new.push(first.to_ascii_uppercase());
317+
second.iter().for_each(|x| new.push(x.to_ascii_lowercase()));
318+
}
319+
new
320+
}
321+
308322
pub fn hex(&self, vm: &VirtualMachine) -> PyResult {
309323
let bla = self
310324
.elements
@@ -346,4 +360,39 @@ impl PyByteInner {
346360
.map(|x| x.unwrap())
347361
.collect::<Vec<u8>>())
348362
}
363+
364+
pub fn center(&self, width: &BigInt, fillbyte: u8, _vm: &VirtualMachine) -> Vec<u8> {
365+
let width = width.to_usize().unwrap();
366+
367+
// adjust right et left side
368+
if width <= self.len() {
369+
return self.elements.clone();
370+
}
371+
let diff: usize = width - self.len();
372+
let mut ln: usize = diff / 2;
373+
let mut rn: usize = ln;
374+
375+
if diff.is_odd() && self.len() % 2 == 0 {
376+
ln += 1
377+
}
378+
379+
if diff.is_odd() && self.len() % 2 != 0 {
380+
rn += 1
381+
}
382+
383+
// merge all
384+
let mut res = vec![fillbyte; ln];
385+
res.extend_from_slice(&self.elements[..]);
386+
res.extend_from_slice(&vec![fillbyte; rn][..]);
387+
388+
res
389+
}
390+
}
391+
392+
pub fn is_byte(obj: &PyObjectRef) -> Option<Vec<u8>> {
393+
match_class!(obj.clone(),
394+
395+
i @ PyBytes => Some(i.get_value().to_vec()),
396+
j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()),
397+
_ => None)
349398
}

vm/src/obj/objbytes.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::ops::Deref;
77
use crate::function::OptionalArg;
88
use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
99

10-
use super::objbyteinner::PyByteInner;
10+
use super::objbyteinner::{is_byte, PyByteInner};
1111
use super::objiter;
1212
use super::objslice::PySlice;
1313
use super::objtype::PyClassRef;
@@ -34,6 +34,10 @@ impl PyBytes {
3434
inner: PyByteInner { elements },
3535
}
3636
}
37+
38+
pub fn get_value(&self) -> &[u8] {
39+
&self.inner.elements
40+
}
3741
}
3842

3943
impl Deref for PyBytes {
@@ -209,6 +213,11 @@ impl PyBytesRef {
209213
Ok(vm.ctx.new_bytes(self.inner.upper(vm)))
210214
}
211215

216+
#[pymethod(name = "capitalize")]
217+
fn capitalize(self, vm: &VirtualMachine) -> PyResult {
218+
Ok(vm.ctx.new_bytes(self.inner.capitalize(vm)))
219+
}
220+
212221
#[pymethod(name = "hex")]
213222
fn hex(self, vm: &VirtualMachine) -> PyResult {
214223
self.inner.hex(vm)
@@ -224,6 +233,42 @@ impl PyBytesRef {
224233
obj => Err(vm.new_type_error(format!("fromhex() argument must be str, not {}", obj )))
225234
)
226235
}
236+
237+
#[pymethod(name = "center")]
238+
fn center(
239+
self,
240+
width: PyObjectRef,
241+
fillbyte: OptionalArg<PyObjectRef>,
242+
vm: &VirtualMachine,
243+
) -> PyResult {
244+
let sym = if let OptionalArg::Present(v) = fillbyte {
245+
match is_byte(&v) {
246+
Some(x) => {
247+
if x.len() == 1 {
248+
x[0]
249+
} else {
250+
return Err(vm.new_type_error(format!(
251+
"center() argument 2 must be a byte string of length 1, not {}",
252+
&v
253+
)));
254+
}
255+
}
256+
None => {
257+
return Err(vm.new_type_error(format!(
258+
"center() argument 2 must be a byte string of length 1, not {}",
259+
&v
260+
)));
261+
}
262+
}
263+
} else {
264+
32 // default is space
265+
};
266+
267+
match_class!(width,
268+
i @PyInt => Ok(vm.ctx.new_bytes(self.inner.center(i.as_bigint(), sym, vm))),
269+
obj => {Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)))}
270+
)
271+
}
227272
}
228273

229274
#[derive(Debug)]

vm/src/obj/objstr.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,8 +694,22 @@ impl PyString {
694694
) -> PyResult<String> {
695695
let value = &self.value;
696696
let rep_char = Self::get_fill_char(&rep, vm)?;
697-
let left_buff: usize = (len - value.len()) / 2;
698-
let right_buff = len - value.len() - left_buff;
697+
let value_len = self.value.chars().count();
698+
699+
if len <= value_len {
700+
return Ok(value.to_string());
701+
}
702+
let diff: usize = len - value_len;
703+
let mut left_buff: usize = diff / 2;
704+
let mut right_buff: usize = left_buff;
705+
706+
if diff % 2 != 0 && value_len % 2 == 0 {
707+
left_buff += 1
708+
}
709+
710+
if diff % 2 != 0 && value_len % 2 != 0 {
711+
right_buff += 1
712+
}
699713
Ok(format!(
700714
"{}{}{}",
701715
rep_char.repeat(left_buff),

0 commit comments

Comments
 (0)