1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
use encoding8;
use opts::{self, Error::ConflictingOption};
use results::{Error, Result};
#[derive(Debug, Clone, Default)]
/// A converter modifies the blocks read from a [`Reader`][super::read::Reader]
/// before they are written to a [`Writer`][super::write::Writer]
pub struct Converter {
    /// The text encoding to convert to, if any
    pub encode_to: EncodeTo,
    /// The case to convert text to, if any
    pub case: Case,
    /// Whether or not to swap pairs of bytes
    pub swap_bytes: bool,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
/// The case to convert to. The effect of this is dependent on [`EncodeTo`]; the
/// conversion will happen AFTER the encoding in the case of
/// [`EncodeTo::ASCII`], [`EncodeTo::OLD_ASCII`], and [`EncodeTo::None`] and
/// **before** otherwise.
pub enum Case {
    None,
    Lower,
    Upper,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[allow(non_camel_case_types)]
/// The Encoding to convert to. [`EncodeTo::ASCII`] and [`EncodeTo::OLD_ASCII`]
/// assume that the source file is encoded in classic `EBCDIC`,
/// while `IBM`, `EBCDIC`, `OLD_IBM`, and `OLD_EBCDIC` assume that the source
/// file is encoded in modern (that is, post-1967) ASCII
pub enum EncodeTo {
    None,
    /// Convert **to** ASCII from EBCDIC
    ASCII,
    /// Convert **to** EBCDIC from ASCII
    EBCDIC,
    /// <UNIMPLEMENTD> Convert **to** IBM from ASCII
    IBM,
    /// <UNIMPLEMENTED> Convert **to** 1963 ASCII from EBCDIC
    OLD_ASCII,
    /// <UNIMPLEMENTED> Convert **to** old ebcdic from ASCII
    OLD_EBCDIC,
    /// <UNIMPLEMENTED> Convert **to** OLD_IBM from ASCII
    OLD_IBM,
}
/// A convertslice can modify a slice of `T` in-place, and provides helper
/// methods to create modified copies.
pub trait ConvertSlice<T> {
    /// modify a slice of `T` in-place.
    fn convert_slice(&self, buf: &mut [T]);

    /// modify a copy of `T` created using `clone()`. This is less efficient
    /// than [`convert_copy`][ConvertSlice::convert_copy]
    fn convert_clone(&self, buf: &[T]) -> Vec<T>
    where
        T: Clone + Default,
    {
        let mut dest: Vec<T> = vec![T::default(); buf.len()];

        dest.clone_from_slice(buf);
        self.convert_slice(&mut dest);
        dest
    }
    /// modify a copy of `T`. This is more efficient than
    /// [`convert_clone`][ConvertSlice::convert_clone] but has tighter
    /// bounds.

    fn convert_copy(&self, buf: &[T]) -> Vec<T>
    where
        T: Copy + Default,
    {
        let mut dest: Vec<T> = vec![T::default(); buf.len()];

        dest.clone_from_slice(buf);
        self.convert_slice(&mut dest);
        dest
    }
}

impl Converter {
    pub fn new(o: &opts::Opts) -> Result<Self> {
        Ok(Converter {
            encode_to: EncodeTo::new(&o.cflags)?,
            case: Case::new(&o.cflags)?,
            swap_bytes: o.cflag(opts::flags::Conv::SWAB),
        })
    }
}

impl ConvertSlice<u8> for Converter {
    #[inline]
    fn convert_slice(&self, buf: &mut [u8]) {
        match self.encode_to {
            EncodeTo::ASCII | EncodeTo::OLD_ASCII => {
                // turn to ASCII before switching case
                self.encode_to.convert_slice(buf);
                self.case.convert_slice(buf);
            },
            _ => {
                // assumed to already be ASCII, so switch case first
                self.case.convert_slice(buf);
                self.encode_to.convert_slice(buf);
            },
        };
        if self.swap_bytes {
            swap_pairs(buf)
        }
    }
}
impl<F, T> ConvertSlice<T> for F where F: Fn(T) -> T, T: Copy {
    fn convert_slice(&self, slice: &mut [T]) { slice.iter_mut().for_each(|t| *t = (self)(*t)) }
}

impl Default for EncodeTo {
    fn default() -> Self { EncodeTo::None }
}

impl Case {
    pub fn new(c: &opts::flags::Conv) -> Result<Self> {
        match (
            c.contains(opts::flags::Conv::LCASE),
            c.contains(opts::flags::Conv::UCASE),
        ) {
            (true, false) => Ok(Case::Lower),
            (false, true) => Ok(Case::Upper),
            (false, false) => Ok(Case::None),
            (true, true) => Err(Error::from(ConflictingOption(
                "options 'lcase' and  'ucase' are mutually exclusives",
            ))),
        }
    }
}

impl ConvertSlice<u8> for Case {
    fn convert_slice(&self, slice: &mut [u8]) {
        match self {
            Case::Lower => slice.iter_mut().for_each(u8::make_ascii_lowercase),
            Case::Upper => slice.iter_mut().for_each(u8::make_ascii_uppercase),
            Case::None => {},
        }
    }
}

/// Swap every pair of items.  If `slice.len() % 2 == 1`, the last item will be
/// ignored.
pub fn swap_pairs<T>(slice: &mut [T]) { (0..(slice.len() / 2)).for_each(|i| slice.swap(2 * i, 2 * i + 1)) }

impl EncodeTo {
    fn new(c: &opts::flags::Conv) -> Result<Self> {
        use opts::flags::Conv;
        match (
            c.contains(Conv::ASCII),
            c.contains(Conv::EBCDIC),
            c.contains(Conv::IBM),
            c.contains(Conv::OLDASCII),
            c.contains(Conv::OLDIBM),
            c.contains(Conv::OLDEBCDIC),
        ) {
            (true, false, false, false, false, false) => Ok(EncodeTo::ASCII),
            (false, true, false, false, false, false) => Ok(EncodeTo::EBCDIC),
            (false, false, true, false, false, false) => Ok(EncodeTo::IBM),
            (false, false, false, true, false, false) => Ok(EncodeTo::OLD_ASCII),
            (false, false, false, false, true, false) => Ok(EncodeTo::OLD_EBCDIC),
            (false, false, false, false, false, true) => Ok(EncodeTo::OLD_IBM),
            (false, false, false, false, false, false) => Ok(EncodeTo::None),
            _ => Err(Error::from(ConflictingOption(concat!(
                "must specify at most one of the following conversion flags: ",
                "'ascii', 'ebcdic', 'ibm', 'old_ascii', 'old_ebcdic', 'old_imb'"
            )))),
        }
    }
}

impl Default for Case {
    fn default() -> Self { Case::None }
}

impl ConvertSlice<u8> for EncodeTo {
    fn convert_slice(&self, s: &mut [u8]) {
        match self {
            EncodeTo::ASCII => s.iter_mut().for_each(encoding8::ebcdic::make_ascii),
            EncodeTo::EBCDIC => s.iter_mut().for_each(encoding8::ascii::make_ebcdic),
            EncodeTo::None => {},
            encoding => unimplemented!("EncodeTo::{:?}.convert_slice", encoding),
        }
    }
}