summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-01-15 11:20:12 +0800
committerGitHub <[email protected]>2026-01-15 11:20:12 +0800
commitb8c02f4d81b0aceb976401f3ce20605f4ee775cb (patch)
tree21acfd3dc03d8ab4c5ed8e911340f0390b3ba3f1
parent6a282204b4d2d27f987ff6b9c2ebf7a680ab7971 (diff)
parent5e6df0bcfe9768e84545cd94466973435268aef1 (diff)
Merge pull request #7219 from xmake-io/iconv
Add cli.iconv module
-rw-r--r--core/src/xmake/engine.c2
-rw-r--r--core/src/xmake/io/file_convert.c169
-rw-r--r--core/src/xmake/string/convert.c58
-rw-r--r--core/src/xmake/utils/charset.c82
-rw-r--r--core/src/xmake/utils/charset.h54
-rw-r--r--core/src/xmake/utils/prefix.h29
-rwxr-xr-xcore/src/xmake/xmake.sh1
-rw-r--r--tests/modules/io/test.lua66
-rw-r--r--xmake/core/base/io.lua27
-rw-r--r--xmake/core/sandbox/modules/io.lua11
-rw-r--r--xmake/modules/cli/iconv.lua69
11 files changed, 513 insertions, 55 deletions
diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c
index d46e30a42..532be1adc 100644
--- a/core/src/xmake/engine.c
+++ b/core/src/xmake/engine.c
@@ -174,6 +174,7 @@ tb_int_t xm_io_file_rawfd(lua_State *lua);
tb_int_t xm_io_file_write(lua_State *lua);
tb_int_t xm_io_file_flush(lua_State *lua);
tb_int_t xm_io_file_close(lua_State *lua);
+tb_int_t xm_io_file_convert(lua_State *lua);
tb_int_t xm_io_file_isatty(lua_State *lua);
// the io/filelock functions
@@ -480,6 +481,7 @@ static luaL_Reg const g_io_functions[] = {
{ "file_flush", xm_io_file_flush },
{ "file_isatty", xm_io_file_isatty },
{ "file_close", xm_io_file_close },
+ { "file_convert", xm_io_file_convert },
{ "file_rawfd", xm_io_file_rawfd },
{ "filelock_open", xm_io_filelock_open },
{ "filelock_lock", xm_io_filelock_lock },
diff --git a/core/src/xmake/io/file_convert.c b/core/src/xmake/io/file_convert.c
new file mode 100644
index 000000000..6901a9a08
--- /dev/null
+++ b/core/src/xmake/io/file_convert.c
@@ -0,0 +1,169 @@
+/*!A cross-platform build utility based on Lua
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2015-present, Xmake Open Source Community.
+ *
+ * @author ruki
+ * @file file_convert.c
+ *
+ */
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * trace
+ */
+#define TB_TRACE_MODULE_NAME "file_convert"
+#define TB_TRACE_MODULE_DEBUG (0)
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * includes
+ */
+#include "prefix.h"
+#include "../utils/charset.h"
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * implementation
+ */
+
+/* convert file
+ *
+ * @param srcpath the srcpath
+ * @param dstpath the dstpath
+ * @param ftype the from-charset type, e.g. ascii, gb2312, gbk, ios8859, ucs2, ucs4, utf8, utf16, utf32
+ * @param ttype the to-charset type
+ *
+ * @code
+ * io.convert(srcpath, dstpath, "gbk", "utf8")
+ * io.convert(srcpath, dstpath, "utf8", "gb2312")
+ * @endcode
+ */
+tb_int_t xm_io_file_convert(lua_State *lua) {
+ // check
+ tb_assert_and_check_return_val(lua, 0);
+
+#ifdef TB_CONFIG_MODULE_HAVE_CHARSET
+ // get arguments
+ tb_char_t const *srcpath = luaL_checkstring(lua, 1);
+ tb_char_t const *dstpath = luaL_checkstring(lua, 2);
+ tb_char_t const *fname = luaL_checkstring(lua, 3);
+ tb_char_t const *tname = luaL_checkstring(lua, 4);
+ tb_check_return_val(srcpath && dstpath && fname && tname, 0);
+
+ // get charset type
+ tb_size_t ftype = TB_CHARSET_TYPE_UTF8;
+ tb_size_t ttype = TB_CHARSET_TYPE_UTF8;
+ xm_charset_entry_ref_t fentry = xm_charset_find_by_name(fname);
+ if (fentry) ftype = fentry->type;
+ else {
+ lua_pushfstring(lua, "invalid charset: %s", fname);
+ lua_error(lua);
+ }
+ xm_charset_entry_ref_t tentry = xm_charset_find_by_name(tname);
+ if (tentry) ttype = tentry->type;
+ else {
+ lua_pushfstring(lua, "invalid charset: %s", tname);
+ lua_error(lua);
+ }
+
+ // done
+ tb_bool_t ok = tb_false;
+ tb_stream_ref_t istream = tb_null;
+ tb_stream_ref_t ostream = tb_null;
+ tb_stream_ref_t fstream = tb_null;
+ do {
+ // init istream
+ istream = tb_stream_init_from_file(srcpath, TB_FILE_MODE_RO);
+ tb_assert_and_check_break(istream);
+
+ // open istream
+ if (!tb_stream_open(istream)) break;
+
+ // skip bom
+ tb_size_t skip = 0;
+ tb_byte_t* bom_data = tb_null;
+ tb_long_t bom_size = tb_stream_peek(istream, &bom_data, 3);
+ if (bom_size >= 3 && ftype == TB_CHARSET_TYPE_UTF8 && bom_data[0] == 0xef && bom_data[1] == 0xbb && bom_data[2] == 0xbf) {
+ skip = 3;
+ } else if (bom_size >= 2 && (ftype & TB_CHARSET_TYPE_UTF16)) {
+ if (bom_data[0] == 0xff && bom_data[1] == 0xfe) skip = 2;
+ else if (bom_data[0] == 0xfe && bom_data[1] == 0xff) skip = 2;
+ }
+ if (skip > 0 && !tb_stream_skip(istream, skip)) break;
+
+ // init ostream
+ ostream = tb_stream_init_from_file(dstpath, TB_FILE_MODE_RW | TB_FILE_MODE_CREAT | TB_FILE_MODE_TRUNC);
+ tb_assert_and_check_break(ostream);
+
+ // open ostream
+ if (!tb_stream_open(ostream)) break;
+
+ // write bom
+ if (!tb_strcmp(tname, "utf8bom")) {
+ static tb_byte_t const k_bom[] = {0xef, 0xbb, 0xbf};
+ if (!tb_stream_bwrit(ostream, k_bom, 3)) break;
+ } else if (!tb_strcmp(tname, "utf16lebom")) {
+ static tb_byte_t const k_bom[] = {0xff, 0xfe};
+ if (!tb_stream_bwrit(ostream, k_bom, 2)) break;
+ } else if (!tb_strcmp(tname, "utf16bom")) {
+#ifndef TB_WORDS_BIGENDIAN
+ static tb_byte_t const k_bom[] = {0xff, 0xfe};
+#else
+ static tb_byte_t const k_bom[] = {0xfe, 0xff};
+#endif
+ if (!tb_stream_bwrit(ostream, k_bom, 2)) break;
+ }
+
+ // init fstream
+ fstream = tb_stream_init_filter_from_charset(istream, ftype, ttype);
+ tb_assert_and_check_break(fstream);
+
+ // open fstream
+ if (!tb_stream_open(fstream)) break;
+
+ // transfer
+ tb_byte_t data[TB_STREAM_BLOCK_MAXN];
+ while (1) {
+ tb_long_t real = tb_stream_read(fstream, data, sizeof(data));
+ if (real > 0) {
+ if (!tb_stream_bwrit(ostream, data, real)) break;
+ } else if (real == 0) {
+ tb_long_t wait = tb_stream_wait(fstream, TB_STREAM_WAIT_READ, tb_stream_timeout(fstream));
+ if (wait <= 0) break;
+ } else break;
+ }
+
+ // ok
+ ok = tb_true;
+
+ } while (0);
+
+ // exit fstream
+ if (fstream) tb_stream_exit(fstream);
+ fstream = tb_null;
+
+ // exit istream
+ if (istream) tb_stream_exit(istream);
+ istream = tb_null;
+
+ // exit ostream
+ if (ostream) tb_stream_exit(ostream);
+ ostream = tb_null;
+
+ // ok?
+ lua_pushboolean(lua, ok);
+ return 1;
+#else
+ lua_pushboolean(lua, tb_false);
+ return 1;
+#endif
+}
diff --git a/core/src/xmake/string/convert.c b/core/src/xmake/string/convert.c
index b3adaf8b8..3b67cda30 100644
--- a/core/src/xmake/string/convert.c
+++ b/core/src/xmake/string/convert.c
@@ -29,59 +29,7 @@
* includes
*/
#include "prefix.h"
-
-/* //////////////////////////////////////////////////////////////////////////////////////
- * globals
- */
-
-// the charset entry type
-typedef struct __xm_charset_entry_t {
- // the charset type
- tb_size_t type;
-
- // the charset name
- tb_char_t const *name;
-} xm_charset_entry_t, *xm_charset_entry_ref_t;
-
-// the charsets, @note: type & name is sorted
-static xm_charset_entry_t g_charsets[] = {
- { TB_CHARSET_TYPE_ANSI, "ansi" },
- { TB_CHARSET_TYPE_ASCII, "ascii" },
- { TB_CHARSET_TYPE_GB2312, "gb2312" },
- { TB_CHARSET_TYPE_GBK, "gbk" },
- { TB_CHARSET_TYPE_ISO8859, "iso8859" },
- { TB_CHARSET_TYPE_UCS2 | TB_CHARSET_TYPE_NE, "ucs2" },
- { TB_CHARSET_TYPE_UCS4 | TB_CHARSET_TYPE_NE, "ucs4" },
- { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_NE, "utf16" },
- { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_BE, "utf16be" },
- { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_LE, "utf16le" },
- { TB_CHARSET_TYPE_UTF32 | TB_CHARSET_TYPE_NE, "utf32" },
- { TB_CHARSET_TYPE_UTF32 | TB_CHARSET_TYPE_BE, "utf32be" },
- { TB_CHARSET_TYPE_UTF32 | TB_CHARSET_TYPE_LE, "utf32le" },
- { TB_CHARSET_TYPE_UTF8, "utf8" },
-};
-
-/* //////////////////////////////////////////////////////////////////////////////////////
- * finder
- */
-static tb_long_t xm_string_charset_comp_by_name(tb_iterator_ref_t iterator, tb_cpointer_t item, tb_cpointer_t name) {
- return tb_stricmp(((xm_charset_entry_ref_t)item)->name, (tb_char_t const *)name);
-}
-static xm_charset_entry_ref_t xm_string_charset_find_by_name(tb_char_t const *name) {
- // make iterator
- tb_array_iterator_t array_iterator;
- tb_iterator_ref_t iterator =
- tb_array_iterator_init_mem(&array_iterator, g_charsets, tb_arrayn(g_charsets), sizeof(xm_charset_entry_t));
- tb_assert_and_check_return_val(iterator, tb_null);
-
- // find it by the binary search
- tb_size_t itor = tb_binary_find_all_if(iterator, xm_string_charset_comp_by_name, name);
- if (itor != tb_iterator_tail(iterator)) {
- return (xm_charset_entry_ref_t)tb_iterator_item(iterator, itor);
- } else {
- return tb_null;
- }
-}
+#include "../utils/charset.h"
/* //////////////////////////////////////////////////////////////////////////////////////
* implementation
@@ -109,8 +57,8 @@ tb_int_t xm_string_convert(lua_State *lua) {
tb_check_return_val(src_cstr && ftype_cstr && ttype_cstr, 0);
// find charsets
- xm_charset_entry_ref_t fcharset = xm_string_charset_find_by_name(ftype_cstr);
- xm_charset_entry_ref_t tcharset = xm_string_charset_find_by_name(ttype_cstr);
+ xm_charset_entry_ref_t fcharset = xm_charset_find_by_name(ftype_cstr);
+ xm_charset_entry_ref_t tcharset = xm_charset_find_by_name(ttype_cstr);
luaL_argcheck(lua, fcharset, 2, "charset not found");
luaL_argcheck(lua, tcharset, 3, "charset not found");
tb_check_return_val(fcharset && tcharset, 0);
diff --git a/core/src/xmake/utils/charset.c b/core/src/xmake/utils/charset.c
new file mode 100644
index 000000000..0a93d9c9a
--- /dev/null
+++ b/core/src/xmake/utils/charset.c
@@ -0,0 +1,82 @@
+/*!A cross-platform build utility based on Lua
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2015-present, Xmake Open Source Community.
+ *
+ * @author ruki
+ * @file charset.c
+ *
+ */
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * trace
+ */
+#define TB_TRACE_MODULE_NAME "charset"
+#define TB_TRACE_MODULE_DEBUG (0)
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * includes
+ */
+#include "charset.h"
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * globals
+ */
+
+// the charsets, @note: type & name is sorted
+static xm_charset_entry_t g_charsets[] = {
+ { TB_CHARSET_TYPE_ANSI, "ansi" },
+ { TB_CHARSET_TYPE_ASCII, "ascii" },
+ { TB_CHARSET_TYPE_GB2312, "gb2312" },
+ { TB_CHARSET_TYPE_GBK, "gbk" },
+ { TB_CHARSET_TYPE_ISO8859, "iso8859" },
+ { TB_CHARSET_TYPE_UCS2 | TB_CHARSET_TYPE_NE, "ucs2" },
+ { TB_CHARSET_TYPE_UCS4 | TB_CHARSET_TYPE_NE, "ucs4" },
+ { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_NE, "utf16" },
+ { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_BE, "utf16be" },
+ { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_NE, "utf16bom" },
+ { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_LE, "utf16le" },
+ { TB_CHARSET_TYPE_UTF16 | TB_CHARSET_TYPE_LE, "utf16lebom" },
+ { TB_CHARSET_TYPE_UTF32 | TB_CHARSET_TYPE_NE, "utf32" },
+ { TB_CHARSET_TYPE_UTF32 | TB_CHARSET_TYPE_BE, "utf32be" },
+ { TB_CHARSET_TYPE_UTF32 | TB_CHARSET_TYPE_LE, "utf32le" },
+ { TB_CHARSET_TYPE_UTF8, "utf8" },
+ { TB_CHARSET_TYPE_UTF8, "utf8bom" },
+};
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * finder
+ */
+static tb_long_t xm_charset_comp_by_name(tb_iterator_ref_t iterator, tb_cpointer_t item, tb_cpointer_t name) {
+ return tb_stricmp(((xm_charset_entry_ref_t)item)->name, (tb_char_t const *)name);
+}
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * implementation
+ */
+xm_charset_entry_ref_t xm_charset_find_by_name(tb_char_t const *name) {
+ // make iterator
+ tb_array_iterator_t array_iterator;
+ tb_iterator_ref_t iterator =
+ tb_array_iterator_init_mem(&array_iterator, g_charsets, tb_arrayn(g_charsets), sizeof(xm_charset_entry_t));
+ tb_assert_and_check_return_val(iterator, tb_null);
+
+ // find it by the binary search
+ tb_size_t itor = tb_binary_find_all_if(iterator, xm_charset_comp_by_name, name);
+ if (itor != tb_iterator_tail(iterator)) {
+ return (xm_charset_entry_ref_t)tb_iterator_item(iterator, itor);
+ } else {
+ return tb_null;
+ }
+}
diff --git a/core/src/xmake/utils/charset.h b/core/src/xmake/utils/charset.h
new file mode 100644
index 000000000..f23febb94
--- /dev/null
+++ b/core/src/xmake/utils/charset.h
@@ -0,0 +1,54 @@
+/*!A cross-platform build utility based on Lua
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2015-present, Xmake Open Source Community.
+ *
+ * @author ruki
+ * @file charset.h
+ *
+ */
+#ifndef XM_UTILS_CHARSET_H
+#define XM_UTILS_CHARSET_H
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * includes
+ */
+#include "prefix.h"
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * types
+ */
+
+// the charset entry type
+typedef struct __xm_charset_entry_t {
+ // the charset type
+ tb_size_t type;
+
+ // the charset name
+ tb_char_t const *name;
+} xm_charset_entry_t, *xm_charset_entry_ref_t;
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * interfaces
+ */
+
+/* find charset by name
+ *
+ * @param name the charset name
+ *
+ * @return the charset entry
+ */
+xm_charset_entry_ref_t xm_charset_find_by_name(tb_char_t const *name);
+
+#endif
diff --git a/core/src/xmake/utils/prefix.h b/core/src/xmake/utils/prefix.h
new file mode 100644
index 000000000..a9f509e72
--- /dev/null
+++ b/core/src/xmake/utils/prefix.h
@@ -0,0 +1,29 @@
+/*!A cross-platform build utility based on Lua
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2015-present, Xmake Open Source Community.
+ *
+ * @author ruki
+ * @file prefix.h
+ *
+ */
+#ifndef XM_UTILS_PREFIX_H
+#define XM_UTILS_PREFIX_H
+
+/* //////////////////////////////////////////////////////////////////////////////////////
+ * includes
+ */
+#include "../prefix.h"
+
+#endif
diff --git a/core/src/xmake/xmake.sh b/core/src/xmake/xmake.sh
index 8a8f1224c..291001306 100755
--- a/core/src/xmake/xmake.sh
+++ b/core/src/xmake/xmake.sh
@@ -70,6 +70,7 @@ target "xmake"
add_files "sandbox/*.c"
add_files "semver/*.c"
add_files "string/*.c"
+ add_files "utils/*.c"
add_files "tty/*.c"
add_files "binutils/*.c"
add_files "binutils/coff/*.c"
diff --git a/tests/modules/io/test.lua b/tests/modules/io/test.lua
index e4c6f23e1..2b8bad8bf 100644
--- a/tests/modules/io/test.lua
+++ b/tests/modules/io/test.lua
@@ -94,3 +94,69 @@ function test_write(t)
os.tryrm("temp")
end
+
+function test_convert(t)
+ local src = "files/utf8bom-lf-eleof"
+ local dst = "temp/convert_test.txt"
+ os.mkdir("temp")
+
+ -- utf8 to gbk (strip bom)
+ io.convert(src, dst, {from = "utf8", to = "gbk"})
+ local content = io.readfile(dst, {encoding = "binary"})
+ t:are_equal(content, "123\\\n456\n789\n")
+
+ -- gbk to utf8
+ local src_gbk = dst
+ local dst_utf8 = "temp/convert_test_utf8.txt"
+ io.convert(src_gbk, dst_utf8, {from = "gbk", to = "utf8"})
+ content = io.readfile(dst_utf8, {encoding = "binary"})
+ t:are_equal(content, "123\\\n456\n789\n")
+
+ -- utf8 to utf8bom
+ local dst_utf8bom = "temp/convert_test_utf8bom.txt"
+ io.convert(src, dst_utf8bom, {from = "utf8", to = "utf8bom"})
+ content = io.readfile(dst_utf8bom, {encoding = "binary"})
+ t:are_equal(content:sub(1, 3), "\239\187\191")
+ t:are_equal(content:sub(4), "123\\\n456\n789\n")
+
+ -- utf16le to utf8
+ local src_utf16le = "files/utf16le-crlf-neleof"
+ local dst_utf16le_to_utf8 = "temp/convert_test_utf16le_to_utf8.txt"
+ io.convert(src_utf16le, dst_utf16le_to_utf8, {from = "utf16le", to = "utf8"})
+ content = io.readfile(dst_utf16le_to_utf8, {encoding = "binary"})
+ t:are_equal(content, "123\\\r\n456\r\n789")
+
+ -- utf16be to utf8
+ local src_utf16be = "files/utf16be-lf-eleof"
+ local dst_utf16be_to_utf8 = "temp/convert_test_utf16be_to_utf8.txt"
+ io.convert(src_utf16be, dst_utf16be_to_utf8, {from = "utf16be", to = "utf8"})
+ content = io.readfile(dst_utf16be_to_utf8, {encoding = "binary"})
+ t:are_equal(content, "123\\\n456\n789\n")
+
+ -- utf8 to utf16le
+ local dst_utf8_to_utf16le = "temp/convert_test_utf8_to_utf16le.txt"
+ io.convert(src, dst_utf8_to_utf16le, {from = "utf8", to = "utf16le"})
+ content = io.readfile(dst_utf8_to_utf16le, {encoding = "binary"})
+ t:are_equal(content:sub(1, 2), "1\0")
+
+ -- utf8 to utf16be
+ local dst_utf8_to_utf16be = "temp/convert_test_utf8_to_utf16be.txt"
+ io.convert(src, dst_utf8_to_utf16be, {from = "utf8", to = "utf16be"})
+ content = io.readfile(dst_utf8_to_utf16be, {encoding = "binary"})
+ t:are_equal(content:sub(1, 2), "\0001")
+
+ -- utf8 to utf16lebom
+ local dst_utf8_to_utf16lebom = "temp/convert_test_utf8_to_utf16lebom.txt"
+ io.convert(src, dst_utf8_to_utf16lebom, {from = "utf8", to = "utf16lebom"})
+ content = io.readfile(dst_utf8_to_utf16lebom, {encoding = "binary"})
+ t:are_equal(content:sub(1, 2), "\255\254")
+
+ -- utf8 to utf16bom
+ local dst_utf8_to_utf16bom = "temp/convert_test_utf8_to_utf16bom.txt"
+ io.convert(src, dst_utf8_to_utf16bom, {from = "utf8", to = "utf16bom"})
+ content = io.readfile(dst_utf8_to_utf16bom, {encoding = "binary"})
+ local bom = content:sub(1, 2)
+ t:require(bom == "\255\254" or bom == "\254\255")
+
+ os.tryrm("temp")
+end
diff --git a/xmake/core/base/io.lua b/xmake/core/base/io.lua
index f35c01ab3..3afd187d8 100644
--- a/xmake/core/base/io.lua
+++ b/xmake/core/base/io.lua
@@ -598,6 +598,33 @@ function io.close(file)
return (file or io.stdout):close()
end
+-- convert file encoding
+--
+-- @param inputfile the input file path
+-- @param outputfile the output file path
+-- @param opt the options
+-- - from: the input encoding, e.g. utf8, utf8bom, utf16, utf16le, utf16lebom, utf16be, gb2312, gbk, iso8859, ucs2, ucs4, utf32 .. (default: utf8)
+-- - to: the output encoding, e.g. utf8, utf8bom, utf16, utf16le, utf16lebom, utf16be, gb2312, gbk, iso8859, ucs2, ucs4, utf32 .. (default: utf8)
+function io.convert(inputfile, outputfile, opt)
+ opt = opt or {}
+ inputfile = tostring(inputfile)
+ outputfile = tostring(outputfile)
+ local from = opt.from or "utf8"
+ local to = opt.to or "utf8"
+
+ -- try using c implementation first
+ if io.file_convert and io.file_convert(inputfile, outputfile, from, to) then
+ return true
+ end
+
+ -- fallback to lua implementation (slow but works)
+ local content, errors = io.readfile(inputfile, {encoding = from})
+ if not content then
+ return false, errors
+ end
+ return io.writefile(outputfile, content, {encoding = to})
+end
+
-- save object the the given filepath
function io.save(filepath, object, opt)
opt = opt or {}
diff --git a/xmake/core/sandbox/modules/io.lua b/xmake/core/sandbox/modules/io.lua
index 1b15d4495..0cafaa5f2 100644
--- a/xmake/core/sandbox/modules/io.lua
+++ b/xmake/core/sandbox/modules/io.lua
@@ -201,6 +201,17 @@ function sandbox_io.insert(filepath, lineidx, text, opt)
return data
end
+-- convert file encoding
+function sandbox_io.convert(inputfile, outputfile, opt)
+ assert(inputfile and outputfile)
+ inputfile = vformat(inputfile)
+ outputfile = vformat(outputfile)
+ local ok, errors = io.convert(inputfile, outputfile, opt)
+ if not ok then
+ raise(errors)
+ end
+end
+
-- get std file
function sandbox_io.stdfile(filepath)
assert(filepath)
diff --git a/xmake/modules/cli/iconv.lua b/xmake/modules/cli/iconv.lua
new file mode 100644
index 000000000..a494155ed
--- /dev/null
+++ b/xmake/modules/cli/iconv.lua
@@ -0,0 +1,69 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author ruki
+-- @file iconv.lua
+--
+
+-- imports
+import("core.base.option")
+
+-- supported encodings
+local encodings = {"ansi", "ascii", "gb2312", "gbk", "iso8859", "ucs2", "ucs4", "utf16", "utf16be", "utf16bom", "utf16le", "utf16lebom", "utf32", "utf32be", "utf32le", "utf8", "utf8bom"}
+
+-- the options
+local options = {
+ {'f', "from", "kv", "utf8", "The source encoding.", values = encodings},
+ {'t', "to", "kv", "utf8", "The target encoding.", values = encodings},
+ {'l', "list", "k", nil, "List supported encodings."},
+ {'o', "outputfile", "kv", nil, "The output file."},
+ {nil, "inputfile", "v", nil, "The input file."}
+}
+
+-- main entry
+function main(...)
+
+ -- parse arguments
+ local argv = table.pack(...)
+ local args = option.parse(argv, options, "Convert encoding of a file.",
+ "",
+ "Usage: xmake l cli.iconv [options] [inputfile]")
+
+ -- list encodings
+ if args.list then
+ print(table.concat(encodings, " "))
+ return
+ end
+
+ -- get arguments
+ local input_file = assert(args.inputfile, "input file required!")
+ local output_file = args.outputfile
+ local from_code = args.from
+ local to_code = args.to
+
+ -- convert encoding
+ if output_file then
+ io.convert(input_file, output_file, {from = from_code, to = to_code})
+ else
+ local tmpfile = os.tmpfile()
+ io.convert(input_file, tmpfile, {from = from_code, to = to_code})
+ local data = io.readfile(tmpfile, {encoding = "binary"})
+ if data then
+ io.write(data)
+ end
+ os.rm(tmpfile)
+ end
+end