diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
commit | ef24de24a82fe681581cc130f342363c47c0969a (patch) | |
tree | 0d494f7e1a38b95c92426f58fe6eaa877303a86c /vendor/tabled | |
parent | Releasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip |
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/tabled')
209 files changed, 40663 insertions, 0 deletions
diff --git a/vendor/tabled/.cargo-checksum.json b/vendor/tabled/.cargo-checksum.json new file mode 100644 index 000000000..24deb3153 --- /dev/null +++ b/vendor/tabled/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"8acd50c40b8eb734ac267ab12360fbabe188c05aa93a5b68c60f36d42cc0ac59","Cargo.toml":"bac040c9f0a02d93211a8bbdc0fa312113bc9835ede425ae6320d24f487aa990","LICENSE-MIT":"49ad3fe9bc9d4f0798a481e1c73ac833c52b4525c72fb2c4b0cd1a510e88a177","README.md":"04b277bd697992dfef40b34a8631bda6eccb91787884b97ae8cdb7257396d7a3","examples/README.md":"c35447fb5c215769ceb80b9279d9f02df9250fbd12ecc2598ee9ab8f1a082b28","examples/alphabet.rs":"469835de88d8da6e1a7b5b162ec28116da0480cd03a1a3a68df5a7a9416da467","examples/border_text.rs":"87ec5c3726d78aaba8a095b3b0018c3d523373288399b60bf8490d98660a814e","examples/builder.rs":"68bd0a303ace09ec695b4ccc5e024ddb9ae85fceb5e5f601d6589651c319f3d1","examples/builder_index.rs":"42448b10779d751ac0e853c77f46df14b51d169ebb1934dca24ae6f39c105ddf","examples/chess.rs":"8d54501d7286d0a0ff2725859824da2a2879e60e4873ccaa8fde1456ac407898","examples/col_row_macros.rs":"66bfb4bec2ecaebd0aeb1bc5fcc534aa091686eb48d7b0ce886ab1218692eb09","examples/color.rs":"6c6b4522976da5775b5501e26eb8df3f56298bcc1dd48bce6fee6f1b7d869b52","examples/colored_borders.rs":"ba36f5c5deb13be80a6bfd10e58666c9350ea06128a8f9b0b5cf0120351adb91","examples/colored_padding.rs":"5b17a748c48d5630f18cbb394ed41df5ff69e8a1f5b4b1dfbe0ce15041e44e8b","examples/colorization.rs":"e3508840fd8b479219d07395ca283f372574a77c8125d593d974eeb8810493f9","examples/column_names.rs":"c00c1feb459ca3549af13d31351c15e7a6bc671eefc8065597f831e164c620af","examples/compact_table.rs":"2564f8b2b14872e743bc9b240047fb32d07d2ed950d9fceace5f3b2f59c4748d","examples/compact_table_2.rs":"6c384217d99a0a0c603e6992878f8881ebe1c4bb3060535d0213d9fcb2c57e7c","examples/compact_table_3.rs":"d017100154a8952677ccb1dd4b89473f0359c9eacd58ff5de7859ed2b37efd65","examples/concat.rs":"77ea7c4332baf7d10b7c3e99efa5a1fd3fd3ee7e3f1c73737d10422a330cc778","examples/custom_style.rs":"4815e7830b8a3826c35ebdc6dc8c4e52239e11c78f03ad01fa4117ce45d263c5","examples/derive/display_with.rs":"a4c5b6400c3236811d6239225706f59934c72b978d02547f83ef6581e6a04877","examples/derive/inline.rs":"4037cce3819a0c93d78f514005956b95550eb9dcc51b371d1a43af4e582ed0f6","examples/derive/inline_enum.rs":"17cd68807d247269de6cb4e596bc8af0353149b1ce76c7723f8ba2aa573ad034","examples/derive/order.rs":"5f8a7157e7195f6bfc02644fc609a020d07371d74cc5b9288c0616f325a2b237","examples/derive/rename.rs":"9f06397edef8ed7ec0691328ea2053f426fe32bf59a921db0d5312ae6b1893ec","examples/derive/rename_all.rs":"3d1479a0d303f8816f7f7d4b04946a5c2dc16176eb6855bf17b3a201dd43e303","examples/derive/skip.rs":"baed27513011a2369d37fe69de86a5de0b3593dc5279a5b799d6ed06584eafd5","examples/disable.rs":"c7a9801e3fc3c2f2bac4c06fef587c1c06f025d00b4a549ad2d1f9e7a7072431","examples/extended_display.rs":"e58b83b61c937bbd2a4563bfddaa971464e04f5ecc8edef6111693d6855f2e1a","examples/extract.rs":"aafb4706d2a5017284ec1657dd4376feb182291c428319d258a15f3903d60d57","examples/format.rs":"acf136a6bbb9035568a00c664df289ad5ae84a6adf089fb339d4b2fac83667a9","examples/formatting_settings.rs":"fe7ae39ed108c04b760799070c2ec1a76b06e43cf9f78bccfe44cb5a9ae47d1c","examples/grid_colors.rs":"a1a5f72dc4bbee5e1fbe6b318decd0992a3e875ca3a3fb64f1f775816546c3be","examples/height.rs":"5489041a299a02d437a4656ca60446571418eec5c4db12717f59373582d04bda","examples/highlight.rs":"5839e6ef8e338717c21c9a4af452ee76c589337cc7de59eb554e8326a5351464","examples/highlight_color.rs":"f19b723b727ccafa99aba2f4d2449c0289d623f268a9b64c980794da25e4202e","examples/hyperlink.rs":"417f2b0e98046d6b9782542b61539d8ad5bfdcc48d52fb4aa4d1e44ce2a4795d","examples/iter_table.rs":"c06fc593fcaa3d4185bbab9da30bebc655865105ccbc8d2803e0b9e4a791b95c","examples/margin.rs":"6bc26aa50d49218e86331844da30da8b177b038d95444c4e86028b7275eb0577","examples/matrix.rs":"f9729badafbee5b9a86dc66459d19ac79d0b5048a2a819e596b4eb2095ae0ef5","examples/merge_duplicates.rs":"4239dc1d6c64416f2a0af97995b34238c3f52b4a1b785d46e6824eb2b57d6449","examples/merge_duplicates_2.rs":"ffb76058b1138540aec19923f36a1954db57f3bfe1450e4f140af992ab4baee9","examples/nested_table.rs":"eb7f9446ada602a202a146c45241165bc3b4d782c9530c4b201f9af8f609a697","examples/nested_table_2.rs":"786ae17ee8fdf78b2bff47b9ed9c08ab700b866cf07c134b80364bca637cb290","examples/nested_table_3.rs":"59d387bcdaaf61dfb2e2ceaf9242b52b4638c9223a077b63a1805f77732d521b","examples/panel.rs":"bda9781934adf26bc72a0269228fc609be8b84f3af5332f151794639630b032d","examples/pool_table.rs":"92f236d52503f153a6cd524673be0423c9f5860510e057a578015d4343e4929f","examples/pool_table2.rs":"a1fb1f5f25c9652c5cb7ccd0a6cc91e5434cd47ad64bf7bdc0eda6cd3b37edd3","examples/rotate.rs":"d200fc2d93a54c3e5e486768551ff5b0e7e233b24a57c05b2b079617d50849dd","examples/settings_list.rs":"b32d18b4bc475d45e1023522bc257a6af25cc9c6bddda3e651666da183d8f690","examples/shadow.rs":"c983465cad4f5b5d562cd27a4f4e7d7e981b2511a3f8385ce324fdb53a5fa4c7","examples/span.rs":"67a3212f606b1bbc7e19cd134bb94830055ec8c43970d4004783018d7e34801a","examples/split.rs":"2bdb3054e7f1467b47a32becfc287a01773bf00aad1458a84fd2ddb7191c1caa","examples/table.rs":"4321c82f4a5267f7e117ab8aaaab351db0bba5de94f2a99aa1460a7b08d77a54","examples/table_width.rs":"25af1d10b736124d904db71dd9c0797c70862551461556ff4a8796f8a364bf20","examples/table_width_2.rs":"90a713b6cac3f6a9038790ffdb2475d587af20954689733e1b7c5d724a30b95d","src/builder/index_builder.rs":"e679f927c0fb6646c7c5588ed2f0821348cb4e746d7783fcd40ca542d8eb618d","src/builder/mod.rs":"c7dccafb2ff869ddc79710f21dba0dcb80e3e9c01f2954b36de23a13cd68e88c","src/builder/table_builder.rs":"a4b4eb726b04b4ebd1632c25cc3dc04ba757bcfb0f4f53b9432c7c94942252ac","src/grid/colored_config.rs":"71f589246b7ddc446537461edc0fdea8a5a1896caac8e27082eb4b228b19c463","src/grid/compact_multiline_config.rs":"40f5894fc0a4841edf989868b2e59a85a64080a98998fb56e10a8c5bffdb414e","src/grid/dimension/complete_dimension.rs":"af04cac77366095d7a56fd0d5c315ec3263b5602bf608a43bd50508798629c97","src/grid/dimension/complete_dimension_vec_records.rs":"46d305799377420f06a34d4451aefd176dd793048339687d8210d7f590ce927c","src/grid/dimension/const_dimension.rs":"1015fd7d26e3d1cf0530dfd191f90f2da32183761825d8c3b60b99a977a5f14b","src/grid/dimension/mod.rs":"5f462f052904ebae923dee6cd69afede677b117274c55a99acc847440f204450","src/grid/dimension/peekable_dimension.rs":"f2611401c5e13f20fb7d23c50b853443d8cc19f7e497a77e566a317688b1e214","src/grid/dimension/pool_table_dimension.rs":"6dc53142a973bea0d5e6a8f12c0dbcf0b2bd0e2be85e905a32b3f96f0889a3a6","src/grid/dimension/static_dimension.rs":"a16b657f72a5795a949528ea7444e9157aeb5e4e3f08734db8ea68789ffa4136","src/grid/mod.rs":"c4c65a0ad651a4fa39c3e43f22a579d18df9150e04d1a9d6aa1b3ff1271f19a1","src/grid/records/empty_records.rs":"5e6498bfee974ac55c9ff0da30d8d11b9b220d825854aeba9a516f87b884f5c5","src/grid/records/into_records/buf_records.rs":"3bb7179051e87ffc158f6622a6a22b630be4021a7da69e8483fbcecce3c90d6a","src/grid/records/into_records/either_string.rs":"d7707681a12f24dcfbe48db7efaa6394d18bb73735630773563a48ab936213e6","src/grid/records/into_records/limit_column_records.rs":"83fd2c5bef9a0e9411467db91990e75eafee7e4f700ed8319254f88a33156eb7","src/grid/records/into_records/limit_row_records.rs":"93aa0eb4443656cd95c90c7ed990722b5944845d1d458fccc8f560abdadf67d6","src/grid/records/into_records/mod.rs":"569338db61df7059748bd346a18f42258d2b3da63bb047825b470af76c6e4e3e","src/grid/records/into_records/truncate_records.rs":"5809a7d721d8f25213459c7e5cd04c2a1a3d19138d437b812f83eee7aa13dd12","src/grid/records/mod.rs":"6177fb0ed037ee9616d93fb1f721c5c579c9f748ff9be1dbc4b28b5eb4f5be5d","src/grid/records/records_mut.rs":"08ed1e637d4725995ed89adc35337a09988b5b35e3f6456668b57cf548bd5520","src/grid/records/resizable.rs":"21b9ad1ccb9cb7906180408620a24ee86aebe9715dc51336979cce0114e5c010","src/lib.rs":"18ab09730e26d3800070eb77c08c17996c999d95337c84455c3bc31356e0f4de","src/macros/col.rs":"54bfe1e8d498397f78266358004aa03c6b1c6308cc3e40909640124cbf474c50","src/macros/mod.rs":"188dfe6bfc1737165ac8dc2aecef6c3785aff67c850e7f5e8a4b4635a56c0af7","src/macros/row.rs":"61354760659ccde947ae3ade6e1b62a592fbd88b20a6b5e49efe158298d13046","src/settings/alignment/mod.rs":"559bf427cd4d75cb4a1efce40354c8139715320e369d133504a1dff297df00d4","src/settings/cell_option.rs":"e00ade2f6031af0e9e2166da21d6fdd3910df7ac09351ec5900f4444a444126a","src/settings/color/mod.rs":"f9aed796f6455db2aaa5178934b8b39c6e20f497007a488c57f3500d29b1d244","src/settings/concat/mod.rs":"1c6ceb585335d00a197e47694aeadf8868a71d6b386ab43bd76bee30a713a317","src/settings/disable/mod.rs":"e825c646f93ce79b582efa0aa7a2c1b20fb53b54a32cad9230edea231193f21b","src/settings/duplicate/mod.rs":"706e6ce779efcd4df18100b4dda9cec12e1d841348fab758596e85dcf6e6e514","src/settings/extract/mod.rs":"1186ddd4fb75cfe1ff05db1ec1eb777ccebd77e656ffaa1bdb8e2dc2177e70b4","src/settings/format/format_config.rs":"af71c41ff5a0a4bce0f2c4afc4d70f73222d528e7fece8acd918119c0ced02f9","src/settings/format/format_content.rs":"5800cde98baf7e0bc9a9fc685c0f29fa4ce558e7848f0d9038f120c36c627295","src/settings/format/format_positioned.rs":"e00d49ecb32095e56263cc70d4cfe318bfd54a934c0fca194fb91c4fc1c116a2","src/settings/format/mod.rs":"eaa8aebb72978abeddd38e28f208007cb299df6bb0adbd1fbcd8562c0b7578a6","src/settings/formatting/alignment_strategy.rs":"f3f6977bfd691422be5ede98ca45c0c61f70a592b2408d5af668edacadb686c7","src/settings/formatting/charset.rs":"7eff1283911eb10f687112a6ad1a00bead66397bcf03a4d80cc5e87866076b81","src/settings/formatting/justification.rs":"a4034e8dfa12c6e0b80caf627b0d7c266f52cae8701241601dafc7455ec235f7","src/settings/formatting/mod.rs":"19e144bde5dda9b02e56b82702f151ee5b02ed9ccbb1902264c652171e89074b","src/settings/formatting/tab_size.rs":"04f00810468950e3c4e8a94fee918601d98f7bc641577c99aff796b6ebf4bcb2","src/settings/formatting/trim_strategy.rs":"c9b02b07563a5c51451a305e1e4211ec3538838f8a998ebec686b33db7d6430d","src/settings/height/cell_height_increase.rs":"42934beb31f8e5b2343a46338c922ba626d64190ae257f901119ae60b825de12","src/settings/height/cell_height_limit.rs":"34ccb01f94816f5bdf7d03be23c241ef6d96c1a93d7525470ee028e0861ed3f9","src/settings/height/height_list.rs":"ac9c4f92639b0499814adb3b8b6e1f09c0445c2585a8f07d30dc65028115228c","src/settings/height/mod.rs":"6f0396b36581c33407db24fa01d45159b34dcf24de681cbbcbc14149686a7fbd","src/settings/height/table_height_increase.rs":"117f3f3feeca3250873c0d7fb87645ec8f7c6cf4d40a367226e36b8789a3c449","src/settings/height/table_height_limit.rs":"bd7d6bfe4c8be8610bd41d1c8c0d4f8325df57fc2c1fd3e5597ab330a4ebeb8c","src/settings/height/util.rs":"0b3fbdbef24785b26f1da3b14f6ea64e57d78162ef0bb324a67a96b3e0c7346a","src/settings/highlight/mod.rs":"f8849979f10e9c35bdcad394c3a9b377d6083d4c45a4e496ecbd8374edfc0256","src/settings/locator/mod.rs":"e9b60346fedfccddbfba6b4d4271acd1659327af8fd78ae042ad21d37c820c65","src/settings/margin/mod.rs":"b11066ee2ecd81f3804d7ff9c75eebfcad3538fbd4d3669c6b54da84c66abe4d","src/settings/measurement/mod.rs":"0893a1e5693ab819b4ecbb1e898302c1a4f40e52d59a120efdc54a2de6704ea5","src/settings/merge/mod.rs":"1b0f62f323623daecd9865e1b46316363423b4cfe8ced45b0ec3815e59f6a4cd","src/settings/mod.rs":"dc25f44a241b1bf5d975d401f23f3e8c2f94360c13642db82e586a215b2e362c","src/settings/modify.rs":"b757545c21823a6b14a4e5382c538d5a2b70592cee2ede1ad378c1735e12252a","src/settings/object/cell.rs":"34c33393afcbc1109939f3947ef351248c0fb0a88788812e59a3743176183f47","src/settings/object/columns.rs":"0684b9f0f5366a3a1e4b9ce0d6ba4464c1e3f0a4b6f787794c77811da9623828","src/settings/object/frame.rs":"887027d12b36744474c0665677a9ce7cbe4349d9a5bdee36c792607f6ba52e5c","src/settings/object/mod.rs":"8322b812c2b8dd3473cf53a9f7c8bd49b8b4e5cf1c25ce62dd5a22447bebb587","src/settings/object/rows.rs":"e026319a33572c76f05dd6de9164bca233f2b354852dc6d0be269e8da0142a4d","src/settings/object/segment.rs":"551d476c27de4d6626e8496d41275c5a4de5a71e25bff65c9ed0201bcbd36303","src/settings/object/util.rs":"2570c1f89a8a1642ec5d530515b0363cbae9ded0f318a66082ed868b4ff01c38","src/settings/padding/mod.rs":"052c8a00c04476b8d4752b099b0f702e5c6954632f6685c8461bd381663a73a7","src/settings/panel/footer.rs":"b1e022930a2eb41faef837a1e5dd7afa5e28a384c8910d3697b65d27a887b386","src/settings/panel/header.rs":"f0ba5b0c898d7fecba6b4c162d7a39411bf3b484a5e66de96fc886caecd1b44c","src/settings/panel/horizontal_panel.rs":"7af24d240b271df2da4aae7800efc2b1b50d228b4d8d9c6be027bb5ef0186590","src/settings/panel/mod.rs":"8db8ea0659ee44ff6aabb384b341c701074edcc866d191eb43db359ebd37bcc2","src/settings/panel/vertical_panel.rs":"081ed754dfc990475ec6fb6bafa7b6882718c1ff0a9764bd2fad4ebb14da117f","src/settings/peaker/mod.rs":"5d7f82e4dc4d27abd7adcdc3f0f898dd439927ed8dd3ac75d4417cf43b601baa","src/settings/rotate/mod.rs":"0ec15820a2d61c411331899098d83b092af0f232a1354864b3a2cdad259f28d3","src/settings/settings_list.rs":"8b4a5efc821ac1ec01c28e164b7f5540d7c87073b3f139ab125349bfd1010aed","src/settings/shadow/mod.rs":"31addebc3df7cfd33365cd15ba71e3cdccbcc03f986181a91dca5afb71dd05ff","src/settings/span/column.rs":"e9e2a2c7be1bc7862411f494a154c1b701a25a3b18ce916773e90699c7e7966d","src/settings/span/mod.rs":"5acf7cf724546179fc2a80b64207159915e506bdca3e3a229998fbea2728483c","src/settings/span/row.rs":"80fdf9d8bc73896be1d2cede82f071350ac79122ddb342dbfeff9a7189642cdc","src/settings/split/mod.rs":"4f613518d564065862a729edbe2a7cae841d1f67848dcbc7dc540bdacf9ea096","src/settings/style/border.rs":"ec1beac0c5ddbd989a8cf18023254ff903627df5ce56aa116c7f8e2277d0f884","src/settings/style/border_char.rs":"20792f88c06ef8e46164274aecf66fd874bed9730cb01222fb78d2a0687316cd","src/settings/style/border_color.rs":"7c234c08cc9e9e14522a7bcfc767880271e5fb07e659e7329dc7e56aa3b2d298","src/settings/style/border_text.rs":"d9b951e9c894e50a57c50d6b6a2e832cabae27681b26cd2d842eef99888d60c8","src/settings/style/builder.rs":"3c074039dbf2ae429f5eeb43da05ceeaee20383d3f67a02b50c8a917904adb52","src/settings/style/horizontal_line.rs":"3695a779a1296d7b595b19fbebe1e5df631d49a8a6e9b915c191571235cca157","src/settings/style/line.rs":"8d7be44ba415b093481e4f41364edb180cee23f0082b7e1b425f69f7459161a5","src/settings/style/mod.rs":"e0e09789e0fb52e82d11e066183ffcba4645384210b13d3a6c61f13dfac01603","src/settings/style/offset.rs":"de5f6b9159716f70d84361401a37118e154de719c5d02168282a5873a9f94b51","src/settings/style/raw_style.rs":"d16821bc0a107adecf66e9ef210ddadd892281510956b5a1de7c9aed5806d17f","src/settings/style/span_border_correction.rs":"70450272ecb057d1720019c2debe060ee51216027dde3a8c0c132b2bb30beb98","src/settings/style/vertical_line.rs":"f4addb95eec3b4b3984e6b86d83ec74912eec49775284a7154006a345e120380","src/settings/table_option.rs":"1d2ca1f99edf4b2780224ee46283e98f8244dc9eede949c612ccd118e811316b","src/settings/themes/colorization.rs":"6de2c67164e222589b44a9f5cb6332be6c519e0553c1dc35d2bcb9783c4af284","src/settings/themes/column_names.rs":"aeecde8f6d606a3aeff7f9b558af86fed0bd57f2308410d42cd6426f298ce38d","src/settings/themes/mod.rs":"57a1b0e79ad5148f74654bfd9c74c78a8d904fb2398482c95735b4d2f71ccec7","src/settings/width/justify.rs":"f4e5d8c33193b241fee01e20e3df696cb90c3e2c727ed15e1f96aba0d393f7bf","src/settings/width/min_width.rs":"bafaef872844f2c8026c862b1cf3e6842f965ebf004728c7334821a3448cce69","src/settings/width/mod.rs":"2c6d5eb71887fe041bc83c543fad75b39d7d430aae0397cf7453bd0281196c05","src/settings/width/truncate.rs":"a03496dbba6ba6ef330438a6a4ad70ea58e326b5aa0a61ca1f04ed88b6312f73","src/settings/width/util.rs":"8c750305ea2840dbe5810583c998d1b5e55cfd12c58fbc9c0ebc9b8850ee914e","src/settings/width/width_list.rs":"83eafd51cfdd98c532065fb6419c8d7ee0c2e9722496d5b4fd20707da6634bcd","src/settings/width/wrap.rs":"408f50024550f2e501b511ebabfb93a8047940426fd9938e28189a28898e52df","src/tabled.rs":"8004c03fd69452e6c0de42c87bbe387a96d19e7d250d98ac48ebeb6a2057c823","src/tables/compact.rs":"8e5c0c96b3b78f53f2345d1451492c6eec544fcb5db94cb8152fcdf7a31a5b39","src/tables/extended.rs":"8887b125e41083cece14b46fba441a3db1eb3bb9b80f2867db834defcfb0360e","src/tables/iter.rs":"aa68749d6cf9dc92a56c837898f7c5f2f33fc1d5c90592bd9607bddbeaecda1b","src/tables/mod.rs":"5a8bd5e46cacc644e5a8fbcc3b5f02a12e87a6d0fbe19d767228ce3b40b3eec0","src/tables/table.rs":"5443e543b0f94e14a1a0ba3d7ed86bebdd6cc432be19173fac952ecc9a86bdb6","src/tables/table_pool.rs":"7eba1a99d44e256149c9171b1aff767d344cad897f459e6ce86648fec46b977f","src/tables/util/mod.rs":"74e2f0a1e3f6107f61fd6182b70864d87ead2aa964220bbc26ed0d9ad44d4694","src/tables/util/utf8_writer.rs":"93b121191b26d2429c800a95c7860668c32252f5372a9ee2483925226d245fbf","tests/core/builder_test.rs":"899d29e769c5b7a5402a733fe9c594b5b785c005e06e7f692774b7ec3a299a0a","tests/core/compact_table.rs":"282ce7a642eeb78642753494eff5b23efac657e74972554b63a31e7fdad41a1b","tests/core/extended_table_test.rs":"619f2d502a227080e8c36960f95027394b0e501907f8746f533f06aa0d5497cd","tests/core/index_test.rs":"f07633ca9469517d5526600b670d7b5aa71a2060d673f24c5886327a32422bd3","tests/core/iter_table.rs":"4c949c834bc0d821cb7a79f7ba3b44c7d232fd8db88871650e11c5403e62f624","tests/core/mod.rs":"c3a136c3f400bad4fd55d72c50c79fd9f8fe4f542572281c0be35e915d1c34ad","tests/core/pool_table.rs":"43ebeaeede9ea3cba5d8120af2e74292a4acc8400b1d97134ca2b09374f7b8c6","tests/core/table_test.rs":"b80e2ed089338591e58215c09186287c6b76ab424f1479dc487a183886ec0a07","tests/derive/derive_test.rs":"4e75c885dac6542f3f18a30ad341c1892f206f7c35dbe5174030f01c5c558f03","tests/derive/mod.rs":"8b059e0716d6e84fd5b6381c0c05fcea51a1ccffcf6addfb7ec4fc737dff602d","tests/macros/col_row_test.rs":"7c98c49354c1f506a5b5f4c878d0a6cd9085ef86363f86278677c038b99c4405","tests/macros/mod.rs":"e11259b057ec7558a625b5eef5efabb06596af50443134ca54415b2bf5794218","tests/main.rs":"c472107cafaee31edd7379c99542e6e89a1f00af4bc0ac3e052e34bce7813786","tests/matrix/matrix.rs":"3093a8cbd7100eddf472489363e40ef7913589795917fcc194b84591a592074b","tests/matrix/matrix_list.rs":"3ee3e7e98a16f57c6db29e964ec3d6ed6972c186106b51dc554e5b0c06c8076b","tests/matrix/mod.rs":"628e26c50c75ba755c9e5f1bcbf13d06b6b7be427f485647904b74af934b0164","tests/settings/alignment_test.rs":"46442422f73673517c530ddddbf35b99e4e38eca4efb4e59ea56fc0eaa6eaaf1","tests/settings/color_test.rs":"7133eba1b7544e04914f80cab22fc71bb864d6c545ac6e215f7f44b177e25619","tests/settings/colorization.rs":"313296177cb22d8b2b96838c36abd09f5667e5faa85114f52753f5e94afa6311","tests/settings/column_names_test.rs":"36766e2bf07f292f1ce22439ec7e09613a818697e197fce3335a51b64b8f6b31","tests/settings/concat_test.rs":"b6ef3a5699cdd5b02b7c8ce243eac81f1a721cd15741d35ce0a9c2319983e14a","tests/settings/disable_test.rs":"c64a08ae99ae0e6ad8fb3b74afbb4d6fda2898bd4504cf8df63d32b33eda5880","tests/settings/duplicate_test.rs":"c22637f808340e59d8a6e199ba895d8c205e1ab0084349760f3efb7f8a465bd0","tests/settings/extract_test.rs":"d675a66ddec3366a3dccacf9e825d9ca3af2656d67e8c7ef3745f3663effb4de","tests/settings/format_test.rs":"06f3297f6a1a54a33d4b399a66bd0a18df2de96f6be0a84bd7a8fe7abb8db817","tests/settings/formatting_test.rs":"f7028d21403f6b3a40fa5eadc9b9aa61b528b6c131e2269affa2c79552d9e18c","tests/settings/height_test.rs":"d5cb6f3f637c59f2ad516773b45e2971a103287e412700a7232b656f61e383e9","tests/settings/highlingt_test.rs":"f45847c75aa66e44f97d7a1eb3d59e659d58df4ca1abe66e4c42caaf9d3810ce","tests/settings/margin_test.rs":"849a4a1bae77e8b4043668a43b7286b45040836cb884f7943c23a4ec46d886be","tests/settings/merge_test.rs":"6da97efe49db8a609bbbc04321363a5fb6aa38b8142d2537f72697e706c819e7","tests/settings/mod.rs":"ad3c772baf4f36dfb6a0951a9daa342ab964aae7744e8a5f66f07a8f0817f864","tests/settings/padding_test.rs":"ab87e0a319242d35439b12f018eafd683cf6017d2b5e9987c7e0fbd6b306feb4","tests/settings/panel_test.rs":"40e93f7e6f79391e49c5856a8554722ae0fb202907c87394b273c1efccc7a37c","tests/settings/render_settings.rs":"57ca840b94a9c5f42d492831a236592d1b1551b65166d6f88382c8ff1e80cd1e","tests/settings/rotate_test.rs":"83a34095d961618b433979454ba92bd0a00e41777326d278f9ee703f0ea77dcb","tests/settings/shadow_test.rs":"a5c4f13be24f0bc051f46520b7316a5670c5ccac77933267577068708ee7334e","tests/settings/span_test.rs":"9c4a2dea81d437327f85f5721654c415fbd705bc9d4fcf277d846ff941081729","tests/settings/split_test.rs":"a953c5e8393e061e07be4c3ba88cf8761f695d84272ddb00cb5288d7ffdfa798","tests/settings/style_test.rs":"50d629536919603410c20a7da1682956923372da34d38c7f54f5a8c0fbce85d7","tests/settings/width_test.rs":"613840392eeaddd897fac8607fdeb442c2fe2b2026b34316c84662ee1b1eb404"},"package":"4d38d39c754ae037a9bc3ca1580a985db7371cd14f1229172d1db9093feb6739"}
\ No newline at end of file diff --git a/vendor/tabled/Cargo.lock b/vendor/tabled/Cargo.lock new file mode 100644 index 000000000..987b995c3 --- /dev/null +++ b/vendor/tabled/Cargo.lock @@ -0,0 +1,210 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ansi-str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf4578926a981ab0ca955dc023541d19de37112bc24c1a197bd806d3d86ad1d" +dependencies = [ + "ansitok", +] + +[[package]] +name = "ansitok" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83" +dependencies = [ + "nom", + "vte", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "papergrid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8" +dependencies = [ + "ansi-str", + "ansitok", + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabled" +version = "0.13.0" +dependencies = [ + "ansi-str", + "ansitok", + "owo-colors", + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] diff --git a/vendor/tabled/Cargo.toml b/vendor/tabled/Cargo.toml new file mode 100644 index 000000000..33b699cd6 --- /dev/null +++ b/vendor/tabled/Cargo.toml @@ -0,0 +1,367 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.61.0" +name = "tabled" +version = "0.13.0" +authors = ["Maxim Zhiburt <zhiburt@gmail.com>"] +description = "An easy to use library for pretty print tables of Rust `struct`s and `enum`s." +homepage = "https://github.com/zhiburt/tabled" +documentation = "https://docs.rs/tabled" +readme = "README.md" +keywords = [ + "table", + "print", + "pretty-table", + "format", + "terminal", +] +categories = [ + "text-processing", + "visualization", +] +license = "MIT" +repository = "https://github.com/zhiburt/tabled" + +[package.metadata.docs.rs] +all-features = true +rustc-args = [ + "--cfg", + "docsrs", +] +rustdoc-args = [ + "--cfg", + "docsrs", +] +targets = ["x86_64-unknown-linux-gnu"] + +[[example]] +name = "color" +required-features = [ + "color", + "derive", +] + +[[example]] +name = "colored_borders" +required-features = ["derive"] + +[[example]] +name = "colored_padding" +path = "examples/colored_padding.rs" +required-features = [ + "color", + "derive", +] + +[[example]] +name = "disable" +required-features = ["derive"] + +[[example]] +name = "rename_all" +path = "examples/derive/rename_all.rs" +required-features = ["derive"] + +[[example]] +name = "rename" +path = "examples/derive/rename.rs" +required-features = ["derive"] + +[[example]] +name = "order" +path = "examples/derive/order.rs" +required-features = ["derive"] + +[[example]] +name = "skip" +path = "examples/derive/skip.rs" +required-features = ["derive"] + +[[example]] +name = "inline" +path = "examples/derive/inline.rs" +required-features = ["derive"] + +[[example]] +name = "inline_enum" +path = "examples/derive/inline_enum.rs" +required-features = ["derive"] + +[[example]] +name = "display_with" +path = "examples/derive/display_with.rs" +required-features = ["derive"] + +[[example]] +name = "table" +path = "examples/table.rs" +required-features = ["derive"] + +[[example]] +name = "builder_index" +path = "examples/builder_index.rs" +required-features = ["derive"] + +[[example]] +name = "concat" +path = "examples/concat.rs" +required-features = ["derive"] + +[[example]] +name = "custom_style" +path = "examples/custom_style.rs" +required-features = ["derive"] + +[[example]] +name = "extended_display" +path = "examples/extended_display.rs" +required-features = ["derive"] + +[[example]] +name = "extract" +path = "examples/extract.rs" +required-features = ["derive"] + +[[example]] +name = "format" +path = "examples/format.rs" +required-features = ["derive"] + +[[example]] +name = "panel" +path = "examples/panel.rs" +required-features = ["derive"] + +[[example]] +name = "rotate" +path = "examples/rotate.rs" +required-features = ["derive"] + +[[example]] +name = "shadow" +path = "examples/shadow.rs" +required-features = ["macros"] + +[[example]] +name = "nested_table_2" +path = "examples/nested_table_2.rs" +required-features = ["derive"] + +[[example]] +name = "nested_table_3" +path = "examples/nested_table_3.rs" +required-features = ["derive"] + +[[example]] +name = "col_row_macros" +path = "examples/col_row_macros.rs" +required-features = [ + "macros", + "derive", +] + +[[example]] +name = "merge_duplicates" +path = "examples/merge_duplicates.rs" +required-features = ["derive"] + +[[example]] +name = "merge_duplicates_2" +path = "examples/merge_duplicates_2.rs" +required-features = ["derive"] + +[[example]] +name = "hyperlink" +path = "examples/hyperlink.rs" +required-features = [ + "derive", + "color", +] + +[[example]] +name = "highlight" +path = "examples/highlight.rs" +required-features = ["std"] + +[[example]] +name = "highlight_color" +path = "examples/highlight_color.rs" +required-features = ["std"] + +[[example]] +name = "border_text" +path = "examples/border_text.rs" +required-features = ["std"] + +[[example]] +name = "span" +path = "examples/span.rs" +required-features = ["std"] + +[[example]] +name = "nested_table" +path = "examples/nested_table.rs" +required-features = ["std"] + +[[example]] +name = "builder" +path = "examples/builder.rs" +required-features = ["std"] + +[[example]] +name = "table_width" +path = "examples/table_width.rs" +required-features = ["std"] + +[[example]] +name = "table_width_2" +path = "examples/table_width_2.rs" +required-features = ["std"] + +[[example]] +name = "height" +path = "examples/height.rs" +required-features = ["std"] + +[[example]] +name = "margin" +path = "examples/margin.rs" +required-features = ["std"] + +[[example]] +name = "iter_table" +path = "examples/iter_table.rs" +required-features = ["std"] + +[[example]] +name = "matrix" +path = "examples/matrix.rs" +required-features = ["std"] + +[[example]] +name = "formatting_settings" +path = "examples/formatting_settings.rs" +required-features = ["std"] + +[[example]] +name = "settings_list" +path = "examples/settings_list.rs" +required-features = ["derive"] + +[[example]] +name = "grid_colors" +path = "examples/grid_colors.rs" +required-features = ["derive"] + +[[example]] +name = "compact_table" +path = "examples/compact_table.rs" +required-features = [] + +[[example]] +name = "compact_table_2" +path = "examples/compact_table_2.rs" +required-features = [] + +[[example]] +name = "alphabet" +path = "examples/alphabet.rs" +required-features = ["std"] + +[[example]] +name = "split" +path = "examples/split.rs" +required-features = [ + "std", + "macros", +] + +[[example]] +name = "pool_table" +path = "examples/pool_table.rs" +required-features = ["std"] + +[[example]] +name = "pool_table2" +path = "examples/pool_table2.rs" +required-features = ["std"] + +[[example]] +name = "column_names" +path = "examples/column_names.rs" +required-features = [ + "std", + "derive", +] + +[[example]] +name = "colorization" +path = "examples/colorization.rs" +required-features = [ + "std", + "derive", +] + +[[example]] +name = "chess" +path = "examples/chess.rs" +required-features = ["std"] + +[dependencies.ansi-str] +version = "0.8" +optional = true + +[dependencies.ansitok] +version = "0.2" +optional = true + +[dependencies.papergrid] +version = "0.10" +default-features = false + +[dependencies.tabled_derive] +version = "0.6" +optional = true + +[dependencies.unicode-width] +version = "0.1" + +[dev-dependencies.owo-colors] +version = "3.5" + +[features] +color = [ + "papergrid/color", + "ansi-str", + "ansitok", + "std", +] +default = [ + "derive", + "macros", +] +derive = [ + "tabled_derive", + "std", +] +macros = ["std"] +std = ["papergrid/std"] + +[badges.coveralls] +branch = "master" +repository = "https://github.com/zhiburt/tabled" +service = "github" + +[badges.maintenance] +status = "actively-developed" diff --git a/vendor/tabled/LICENSE-MIT b/vendor/tabled/LICENSE-MIT new file mode 100644 index 000000000..6077b9d68 --- /dev/null +++ b/vendor/tabled/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Maxim Zhiburt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/tabled/README.md b/vendor/tabled/README.md new file mode 100644 index 000000000..0bcc6ef27 --- /dev/null +++ b/vendor/tabled/README.md @@ -0,0 +1,92 @@ +[<img alt="github" src="https://img.shields.io/badge/github-zhiburt/tabled-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/zhiburt/tabled/) +[<img alt="crates.io" src="https://img.shields.io/crates/v/tabled.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/tabled) +[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-tabled-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/tabled) +[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/zhiburt/tabled/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/zhiburt/tabled/actions) +[<img alt="coverage" src="https://img.shields.io/coveralls/github/zhiburt/tabled/master?style=for-the-badge" height="20">](https://coveralls.io/github/zhiburt/tabled) +[<img alt="dependency status" src="https://deps.rs/repo/github/zhiburt/tabled/status.svg?style=for-the-badge" height="20">](https://deps.rs/repo/github/zhiburt/tabled) + +# tabled + +An easy to use library for pretty printing tables of Rust `struct`s and `enum`s. + +There are more examples and you can find in this [`README`](https://github.com/zhiburt/tabled/blob/master/README.md). + +## Usage + +To print a list of structs or enums as a table your types should implement the the `Tabled` trait or derive it with a `#[derive(Tabled)]` macro. +Most of the default types implement the trait out of the box. + +Most of a table configuration can be found in [`tabled::settings`](https://docs.rs/tabled/latest/tabled/settings/index.html) module. + +```rust +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +struct Language { + name: String, + designed_by: String, + invented_year: usize, +} + +impl Language { + fn new(name: &str, designed_by: &str, invented_year: usize) -> Self { + Self { + name: name.to_string(), + designed_by: designed_by.to_string(), + invented_year, + } + } +} + +let languages = vec![ + Language::new("C", "Dennis Ritchie", 1972), + Language::new("Go", "Rob Pike", 2009), + Language::new("Rust", "Graydon Hoare", 2010), + Language::new("Hare", "Drew DeVault", 2022), +]; + +let table = Table::new(languages).to_string(); + +assert_eq!( + table, + "+------+----------------+---------------+\n\ + | name | designed_by | invented_year |\n\ + +------+----------------+---------------+\n\ + | C | Dennis Ritchie | 1972 |\n\ + +------+----------------+---------------+\n\ + | Go | Rob Pike | 2009 |\n\ + +------+----------------+---------------+\n\ + | Rust | Graydon Hoare | 2010 |\n\ + +------+----------------+---------------+\n\ + | Hare | Drew DeVault | 2022 |\n\ + +------+----------------+---------------+" +); +``` + +The same example but we are building a table step by step. + +```rust +use tabled::{builder::Builder, settings::Style}; + +let mut builder = Builder::new(); +builder.push_record(["C", "Dennis Ritchie", "1972"]); +builder.push_record(["Go", "Rob Pike", "2009"]); +builder.push_record(["Rust", "Graydon Hoare", "2010"]); +builder.push_record(["Hare", "Drew DeVault", "2022"]); + +let table = builder.build() + .with(Style::ascii_rounded()) + .to_string(); + +assert_eq!( + table, + concat!( + ".------------------------------.\n", + "| C | Dennis Ritchie | 1972 |\n", + "| Go | Rob Pike | 2009 |\n", + "| Rust | Graydon Hoare | 2010 |\n", + "| Hare | Drew DeVault | 2022 |\n", + "'------------------------------'" + ) +); +```
\ No newline at end of file diff --git a/vendor/tabled/examples/README.md b/vendor/tabled/examples/README.md new file mode 100644 index 000000000..51c9d2e1c --- /dev/null +++ b/vendor/tabled/examples/README.md @@ -0,0 +1,831 @@ +This file contains an overview of examples. + +- `derive` folder contains a list of examples which uses different `#[derive(Tabled)]` attributes. +- `show` folder contains a program which uses different styles and prints the resulting table. +- `terminal_size` folder contains a program which spreads the table to the max terminal width and max terminal height. + You can use which dimension to use via args `--width`, `--height` by default 2 are used. + +Bellow there's a list of results of running some examples. + +## table + +``` +| name | based_on | is_active | is_cool | +|---------|----------|-----------|---------| +| Manjaro | Arch | true | true | +| Arch | | true | true | +| Debian | | true | true | +``` + +## border_text + +``` + Numbers ─┬────┬────┬────┐ +│ 0 │ 1 │ 2 │ 3 │ 4 │ + More numbers ─┼────┼────┤ +│ 5 │ 6 │ 7 │ 8 │ 9 │ +│ 10 │ 11 │ 12 │ 13 │ 14 │ + end. ────┴────┴────┴────┘ +``` + +## builder_index + +``` +┌───────────┬─────────┬──────┬────────┐ +│ │ Manjaro │ Arch │ Debian │ +├───────────┼─────────┼──────┼────────┤ +│ based_on │ Arch │ None │ None │ +├───────────┼─────────┼──────┼────────┤ +│ is_active │ true │ true │ true │ +├───────────┼─────────┼──────┼────────┤ +│ is_cool │ true │ true │ true │ +└───────────┴─────────┴──────┴────────┘ +``` + +## builder + +``` +| https://en.wikipedia.org/wiki/Ocean | +|---------------+---------------------| +| The terms "the ocean" or "the sea" | +| used without specification refer to | +| the interconnected body of salt wa | +| ter covering the majority of the Ea | +| rth's surface | +| =================================== | +| # | Ocean | +| 0 | Atlantic | +| 1 | Pacific | +| 2 | Indian | +| 3 | Southern | +| 4 | Arctic | +``` + +## chess + +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://github.com/zhiburt/tabled/assets/20165848/f474bc84-5ed4-4f49-b6e9-fc204c3ecd5d"> + <img alt="Preview" src="https://github.com/zhiburt/tabled/assets/20165848/68960bba-c5ba-4e0a-85b4-ba4f36bc0ed0"> +</picture> + +## col_row_macros + +``` ++-------------------------------------------+---------------------------------------------+ +| .---------------------------------------. | ┌────────────────────┬─────┬──────────────┐ | +| | name | age | is_validated | | │ name │ age │ is_validated │ | +| | Jon Doe | 255 | false | | ├────────────────────┼─────┼──────────────┤ | +| | Mark Nelson | 13 | true | | │ Jack Black │ 51 │ false │ | +| | Terminal Monitor | 0 | false | | ├────────────────────┼─────┼──────────────┤ | +| | Adam Blend | 17 | true | | │ Michelle Goldstein │ 44 │ true │ | +| '---------------------------------------' | └────────────────────┴─────┴──────────────┘ | ++-------------------------------------------+---------------------------------------------+ ++-------------------------------------------+ +| .---------------------------------------. | +| | name | age | is_validated | | +| | Jon Doe | 255 | false | | +| | Mark Nelson | 13 | true | | +| | Terminal Monitor | 0 | false | | +| | Adam Blend | 17 | true | | +| '---------------------------------------' | ++-------------------------------------------+ +| .---------------------------------------. | +| | name | age | is_validated | | +| | Jon Doe | 255 | false | | +| | Mark Nelson | 13 | true | | +| | Terminal Monitor | 0 | false | | +| | Adam Blend | 17 | true | | +| '---------------------------------------' | ++-------------------------------------------+ +| .---------------------------------------. | +| | name | age | is_validated | | +| | Jon Doe | 255 | false | | +| | Mark Nelson | 13 | true | | +| | Terminal Monitor | 0 | false | | +| | Adam Blend | 17 | true | | +| '---------------------------------------' | ++-------------------------------------------+ ++-------------------------------------------------------------------------------+ +| +-------+-----+--------------+ ┌────────────────────┬─────┬──────────────┐ | +| | name | age | is_validated | │ name │ age │ is_validated │ | +| +-------+-----+--------------+ ├────────────────────┼─────┼──────────────┤ | +| | Sam | 31 | true | │ Jack Black │ 51 │ false │ | +| +-------+-----+--------------+ ├────────────────────┼─────┼──────────────┤ | +| | Sarah | 26 | true | │ Michelle Goldstein │ 44 │ true │ | +| +-------+-----+--------------+ └────────────────────┴─────┴──────────────┘ | ++-------------------------------------------------------------------------------+ +| .---------------------------------------. | +| | name | age | is_validated | | +| | Jon Doe | 255 | false | | +| | Mark Nelson | 13 | true | | +| | Terminal Monitor | 0 | false | | +| | Adam Blend | 17 | true | | +| '---------------------------------------' | ++-------------------------------------------------------------------------------+ +``` + +## color + +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/zhiburt/tabled/assets/assets/example-color-1-dark.png"> + <img alt="Preview" src="https://raw.githubusercontent.com/zhiburt/tabled/assets/assets/example-color-1-light.png"> +</picture> + +## colored_borders + +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/zhiburt/tabled/assets/assets/example-colored_borders-1-dark.png"> + <img alt="Preview" src="https://raw.githubusercontent.com/zhiburt/tabled/assets/assets/example-colored_borders-1-light.png"> +</picture> + +## colored_padding + +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/zhiburt/tabled/assets/assets/example-padding_color-1-dark.png"> + <img alt="Preview" src="https://raw.githubusercontent.com/zhiburt/tabled/assets/assets/example-padding_color-1-light.png"> +</picture> + +## colorization + +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://github.com/zhiburt/tabled/assets/20165848/95ef3c5b-a86d-425c-b95a-b689b61734c5"> + <img alt="Preview" src="https://github.com/zhiburt/tabled/assets/20165848/b6b8af0a-7c1f-4656-b880-c8ecd130c5be"> +</picture> + +## column_names + + +<picture> + <source media="(prefers-color-scheme: dark)" srcset="https://github.com/zhiburt/tabled/assets/20165848/82d8e285-3c1b-4dd2-a1f4-45fd49f0dabe"> + <img alt="Preview" src="https://github.com/zhiburt/tabled/assets/20165848/13054c74-0fae-4df1-87e8-60d498d734e4"> +</picture> + +## concat + +``` + temperature_c wind_ms latitude longitude + 16 3000 111.111 333.333 + -20 300 5.111 7282.1 + 40 100 0 0 + 0 0 +``` + +## custom_style + +``` +┌────────────────────┬─────────────────────────────────┐ +│ name │ first_release developer │ +├────────────────────┼─────────────────────────────────┤ +│ Sublime Text 3 │ 2008 Sublime HQ │ +│ Visual Studio Code │ 2015 Microsoft │ +│ Notepad++ │ 2003 Don Ho │ +│ GNU Emacs │ 1984 Richard Stallman │ +│ Neovim │ 2015 Vim community │ +└────────────────────┴─────────────────────────────────┘ +``` + +## disable + +``` +########### +# name # based_on | is_cool | +###########----------|---------| +# Debian # | true | +########### +# Arch # | true | +########### +# Manjaro # Arch | true | +########### +``` + +## expanded_display + +``` +-[ RECORD 0 ]------ +name | Manjaro +based_on | Arch +is_active | true +is_cool | true +-[ RECORD 1 ]------ +name | Arch +based_on | +is_active | true +is_cool | true +-[ RECORD 2 ]------ +name | Debian +based_on | +is_active | true +is_cool | true +``` + +## extract + +``` +┌───────────────┬───────────────────────────┬──────────────────┬────────────────────┐ +│ artist │ name │ released │ level_of_greatness │ +├───────────────┼───────────────────────────┼──────────────────┼────────────────────┤ +│ Pink Floyd │ The Dark Side of the Moon │ 01 March 1973 │ Unparalleled │ +├───────────────┼───────────────────────────┼──────────────────┼────────────────────┤ +│ Fleetwood Mac │ Rumours │ 04 February 1977 │ Outstanding │ +├───────────────┼───────────────────────────┼──────────────────┼────────────────────┤ +│ Led Zeppelin │ Led Zeppelin IV │ 08 November 1971 │ Supreme │ +└───────────────┴───────────────────────────┴──────────────────┴────────────────────┘ + +┼───────────────────────────┼──────────────────┼──────────────┤ +│ The Dark Side of the Moon │ 01 March 1973 │ Unparalleled │ +┼───────────────────────────┼──────────────────┼──────────────┤ +│ Rumours │ 04 February 1977 │ Outstanding │ +┼───────────────────────────┼──────────────────┼──────────────┤ + +┌───────────────────────────┬──────────────────┬───────────────┐ +│ The Dark Side of the Moon │ 01 March 1973 │ Unparalleled │ +├───────────────────────────┼──────────────────┼───────────────┤ +│ Rumours │ 04 February 1977 │ +Outstanding+ │ +└───────────────────────────┴──────────────────┴───────────────┘ +``` + +## format + +``` + 0 | 1 | 2 +---------------------------------------------+--------------------------------+------------------------- + 8ae4e8957caeaa467acbce963701e227af00a1c7... | bypass open-source transmitter | index neural panel + 48c76de71bd685486d97dc8f4f05aa6fcc0c3f86... | program online alarm | copy bluetooth card + 6ffc2a2796229fc7bf59471ad907f58b897005d0... | CSV | reboot mobile capacitor +``` + +## formatting_settings + +``` +╭───────────────────╮ +│ &str │ +├───────────────────┤ +│ │ +│ [ │ +│ "foo", │ +│ { │ +│ "bar": 1, │ +│ "baz": [ │ +│ 2, │ +│ 3 │ +│ ] │ +│ } │ +│ ] │ +╰───────────────────╯ + +╭───────────────────╮ +│ &str │ +├───────────────────┤ +│ │ +│ [ │ +│ "foo", │ +│ { │ +│ "bar": 1, │ +│ "baz": [ │ +│ 2, │ +│ 3 │ +│ ] │ +│ } │ +│ ] │ +╰───────────────────╯ + +╭───────────────────╮ +│ &str │ +├───────────────────┤ +│ [ │ +│ "foo", │ +│ { │ +│ "bar": 1, │ +│ "baz": [ │ +│ 2, │ +│ 3 │ +│ ] │ +│ } │ +│ ] │ +│ │ +╰───────────────────╯ +``` + +## highlight + +``` +************* +* 0 │ 1 │ 2 * +*****───***** +│ A * B * C │ +├───*───*───┤ +│ D * E * F │ +├───*───*───┤ +│ G * H * I │ +└───*****───┘ +``` + +## margin + +``` +vvvvvvvvvvvvvvvvvv +vvvvvvvvvvvvvvvvvv +<<<<=== === ===>>> +<<<< 0 1 2 >>> +<<<<=== === ===>>> +<<<< A B C >>> +<<<< D E F >>> +<<<< G H I >>> +<<<<=== === ===>>> +^^^^^^^^^^^^^^^^^^ +``` + +## nested_table + +``` ++-----------------------------------------------+ +| +---------------------+ | +| | Animal | | +| +---------------------+ | +| | +-----------------+ | | +| | | +age: Int | | | +| | | +gender: String | | | +| | +-----------------+ | | +| | +-----------------+ | | +| | | +isMammal() | | | +| | | +mate() | | | +| | +-----------------+ | | +| +---------------------+ | +| ▲ | +| | | +| | | +| +-----------------------------------+ | +| | Duck | | +| +-----------------------------------+ | +| | +-------------------------------+ | | +| | | +beakColor: String = "yellow" | | | +| | +-------------------------------+ | | +| | +-------------------------------+ | | +| | | +swim() | | | +| | | +quack() | | | +| | +-------------------------------+ | | +| +-----------------------------------+ | ++-----------------------------------------------+ +``` + +## nested_table_2 + +``` +┌───────┬─────────────────────────────────────────────────┬──────────────────────────────────────────────┐ +│ name │ main_os │ switch_os │ +├───────┼─────────────────────────────────────────────────┼──────────────────────────────────────────────┤ +│ Azure │ ╔═════════╦═════════════╦═══════════╦═════════╗ │ ╔═════════╦══════════╦═══════════╦═════════╗ │ +│ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ +│ │ ╠═════════╬═════════════╬═══════════╬═════════╣ │ ╠═════════╬══════════╬═══════════╬═════════╣ │ +│ │ ║ Windows ║ Independent ║ true ║ true ║ │ ║ Manjaro ║ Arch ║ true ║ true ║ │ +│ │ ╚═════════╩═════════════╩═══════════╩═════════╝ │ ╚═════════╩══════════╩═══════════╩═════════╝ │ +├───────┼─────────────────────────────────────────────────┼──────────────────────────────────────────────┤ +│ AWS │ ╔════════╦═════════════╦═══════════╦═════════╗ │ ╔══════╦═════════════╦═══════════╦═════════╗ │ +│ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ +│ │ ╠════════╬═════════════╬═══════════╬═════════╣ │ ╠══════╬═════════════╬═══════════╬═════════╣ │ +│ │ ║ Debian ║ Independent ║ true ║ true ║ │ ║ Arch ║ Independent ║ true ║ true ║ │ +│ │ ╚════════╩═════════════╩═══════════╩═════════╝ │ ╚══════╩═════════════╩═══════════╩═════════╝ │ +├───────┼─────────────────────────────────────────────────┼──────────────────────────────────────────────┤ +│ GCP │ ╔════════╦═════════════╦═══════════╦═════════╗ │ ╔══════╦═════════════╦═══════════╦═════════╗ │ +│ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ +│ │ ╠════════╬═════════════╬═══════════╬═════════╣ │ ╠══════╬═════════════╬═══════════╬═════════╣ │ +│ │ ║ Debian ║ Independent ║ true ║ true ║ │ ║ Arch ║ Independent ║ true ║ true ║ │ +│ │ ╚════════╩═════════════╩═══════════╩═════════╝ │ ╚══════╩═════════════╩═══════════╩═════════╝ │ +└───────┴─────────────────────────────────────────────────┴──────────────────────────────────────────────┘ +``` + +## nested_table_3 + +``` +************************************************* +* Thank You * +************************************************* +| +------------+------------------------------+ | +| | Contributors | | +| +------------+------------------------------+ | +| | author | profile | | +| +------------+------------------------------+ | +| | kozmod | https:/github.com/kozmod | | +| +------------+------------------------------+ | +| | IsaacCloos | https:/github.com/IsaacCloos | | +| +------------+------------------------------+ | +| +-----------+-----------------------------+ | +| | Issuers | | +| +-----------+-----------------------------+ | +| | author | profile | | +| +-----------+-----------------------------+ | +| | aharpervc | https:/github.com/aharpervc | | +| +-----------+-----------------------------+ | ++-----------------------------------------------+ +``` + +## panel + +``` +┌───┬────────────────────────────────────────────────────────────────────┬───┐ +│ S │ Tabled Releases │ S │ +│ o │ │ o │ +│ m │ │ m │ +│ e │ │ e │ +│ ├─────────┬────────────────┬───────────┬─────────────────────────────┤ │ +│ t │ version │ published_date │ is_active │ major_feature │ t │ +│ e │ │ │ │ │ e │ +│ x ├─────────┼────────────────┼───────────┼─────────────────────────────┤ x │ +│ t │ 0.2.1 │ 2021-06-23 │ true │ #[header(inline)] attribute │ t │ +│ │ │ │ │ │ │ +│ g ├─────────┼────────────────┼───────────┼─────────────────────────────┤ g │ +│ o │ 0.2.0 │ 2021-06-19 │ false │ API changes │ o │ +│ e │ │ │ │ │ e │ +│ s ├─────────┼────────────────┼───────────┼─────────────────────────────┤ s │ +│ │ 0.1.4 │ 2021-06-07 │ false │ display_with attribute │ │ +│ h │ │ │ │ │ h │ +│ e ├─────────┴────────────────┴───────────┴─────────────────────────────┤ e │ +│ r │ N - 3 │ r │ +│ e │ │ e │ +└───┴────────────────────────────────────────────────────────────────────┴───┘ +``` + +## print_matrix + +``` +┌────┬────┬────┬────┬────┬────┬────┬────┬────┬─────┐ +│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │ 20 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 3 │ 6 │ 9 │ 12 │ 15 │ 18 │ 21 │ 24 │ 27 │ 30 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 4 │ 8 │ 12 │ 16 │ 20 │ 24 │ 28 │ 32 │ 36 │ 40 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 5 │ 10 │ 15 │ 20 │ 25 │ 30 │ 35 │ 40 │ 45 │ 50 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 6 │ 12 │ 18 │ 24 │ 30 │ 36 │ 42 │ 48 │ 54 │ 60 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 7 │ 14 │ 21 │ 28 │ 35 │ 42 │ 49 │ 56 │ 63 │ 70 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 8 │ 16 │ 24 │ 32 │ 40 │ 48 │ 56 │ 64 │ 72 │ 80 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 9 │ 18 │ 27 │ 36 │ 45 │ 54 │ 63 │ 72 │ 81 │ 90 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 10 │ 20 │ 30 │ 40 │ 50 │ 60 │ 70 │ 80 │ 90 │ 100 │ +└────┴────┴────┴────┴────┴────┴────┴────┴────┴─────┘ +``` + +## rotate + +``` ++--------------+------------------------+---------------------------+--------------------------+ +| link | https://getfedora.org/ | https://www.opensuse.org/ | https://endeavouros.com/ | ++--------------+------------------------+---------------------------+--------------------------+ +| destribution | Fedora | OpenSUSE | Endeavouros | ++--------------+------------------------+---------------------------+--------------------------+ +| id | 0 | 2 | 3 | ++--------------+------------------------+---------------------------+--------------------------+ +``` + +## shadow + +``` +┌──┬┐ ╔══╦╗ ╓──┬╖ ╒═╤╕ +│ ││ ║ ║║ ║ │║ │ ││ +├──┼┤ ╠══╬╣ ╟──┼╢ ╞═╪╡ +└──┴┘ ╚══╩╝ ╙──┴╜ ╘═╧╛ +┌──────────────────┐ +│ ╔═══╗ Some text │▒▒ +│ ╚═╦═╝ In the box│▒▒ +╞═╤══╩══╤══════════╡▒▒ +│ ├──┬──┤ │▒▒ +│ └──┴──┘ │▒▒ +└──────────────────┘▒▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +``` + +## span + +``` +┌───────────────────────────────────────────────────────────────────────────────┐ +│ span all 5 columns │ +├───────────────────────────────────────────────────────────────┬───────────────┤ +│ span 4 columns │ just 1 column │ +├───────────────────────────────┬───────────────┬───────────────┼───────────────┤ +│ span 2 columns │ just 1 column │ │ │ +├───────────────┬───────────────┴───────────────┤ just 1 column │ │ +│ just 1 column │ span 2 columns │ span │ just 1 column │ +│ │ span │ 3 │ span │ +├───────────────┤ 2 │ columns │ 4 │ +│ just 1 column │ columns │ │ columns │ +├───────────────┼───────────────┬───────────────┼───────────────┤ │ +│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ │ +└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘ +``` + +## table_width + +``` +| 0 | 1 | +|------------------|-----------| +| Hello World!!! | 3.3.22.2 | +| Guten Morgen | 1.1.1.1 | +| Добры вечар | 127.0.0.1 | +| Bonjour le monde | | +| Ciao mondo | | + +| 0 | 1 | +|------------|-----| +| Hello W... | ... | +| Guten M... | ... | +| Добры в... | ... | +| Bonjour... | | +| Ciao mondo | | + +| 0 | 1 | +|-------|-----| +| Hello | ... | +| W... | | +| Guten | ... | +| M... | | +| Добры | ... | +| в... | | +| Bonjo | | +| ur... | | +| Ciao | | +| mondo | | + +| 0 | 1 | +|---------------|------------| +| Hello | ... | +| W... | | +| Guten | ... | +| M... | | +| Добры | ... | +| в... | | +| Bonjo | | +| ur... | | +| Ciao | | +| mondo | | +``` + +## table_width_2 + +``` +.----------------------------------------. +| usize | &str | +| 0 | # Changelog | +| 1 | All notable changes to this | +| | projectwill be documented in | +| | thisfile. | +| 2 | The format is based on [Keep a | +| | Changelog](https://keepachange | +| | log.com/en/1.0.0/), | +| 3 | and this project adheres to | +| | [SemanticVersioning](https://s | +| | emver.org/spec/v2.0.0.html). | +| 4 | ## Unreleased | +'-------+--------------------------------' +``` + +## alphabet + +``` ++---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | ++---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +``` + +## compact_table + +``` +| Debian | | true | +|-------|-----|-----| +| Arch | | true | +| Manjaro | Arch | true | +``` + +## compact_table_2 + +``` + Debian | 1.1.1.1 | true +---------+-----------+------ + Arch | 127.1.1.1 | true + Manjaro | Arch | true +``` + +## extended_display + +``` +-[ RECORD 0 ]------ +name | Manjaro +based_on | Arch +is_active | true +is_cool | true +-[ RECORD 1 ]------ +name | Arch +based_on | +is_active | true +is_cool | true +-[ RECORD 2 ]------ +name | Debian +based_on | +is_active | true +is_cool | true +``` + +## height + +``` +Table + +| &str | i32 | +|-------------|-----| +| Multi | 123 | +| line | | +| string | | +| Single line | 234 | + +Table increase height to 10 + +| &str | i32 | +| | | +| | | +|-------------|-----| +| Multi | 123 | +| line | | +| string | | +| | | +| Single line | 234 | +| | | + +Table decrease height to 4 + +| &str | i32 | +|-------------|-----| +| Multi | 123 | +| Single line | 234 | +Table decrease height to 0 + +|--|--| +``` + +## iter_table + +``` +.----------------------------------------------------------------------------------------. +| 0 | ok | //! The example can be run by this command | +| 1 | ok | //! `cargo run --example iter_table` | +| 2 | ok | | +| 3 | ok | use std::io::BufRead; | +| 4 | ok | | +| 5 | ok | use tabled::{settings::Style, tables::IterTable}; | +| 6 | ok | | +| 7 | ok | fn main() { | +| 8 | ok | let path = file!(); | +| 9 | ok | let file = std::fs::File::open(path).unwrap(); | +| 10 | ok | let reader = std::io::BufReader::new(file); | +| 11 | ok | let iterator = reader.lines().enumerate().map(|(i, line)| match line { | +| 12 | ok | Ok(line) => [i.to_string(), String::from("ok"), line], | +| 13 | ok | Err(err) => [i.to_string(), String::from("error"), err.to_string()], | +| 14 | ok | }); | +| 15 | ok | | +| 16 | ok | let table = IterTable::new(iterator).with(Style::ascii_rounded()); | +| 17 | ok | | +| 18 | ok | table.build(std::io::stdout()).unwrap(); | +| 19 | ok | println!() | +| 20 | ok | } | +'----------------------------------------------------------------------------------------' +``` + +## margin + +``` +vvvvvvvvvvvvvvvvvv +vvvvvvvvvvvvvvvvvv +<<<<=== === ===>>> +<<<< 0 1 2 >>> +<<<<=== === ===>>> +<<<< A B C >>> +<<<< D E F >>> +<<<< G H I >>> +<<<<=== === ===>>> +^^^^^^^^^^^^^^^^^^ +``` + +## matrix + +``` +┌────┬────┬────┬────┬────┬────┬────┬────┬────┬─────┐ +│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │ 20 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 3 │ 6 │ 9 │ 12 │ 15 │ 18 │ 21 │ 24 │ 27 │ 30 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 4 │ 8 │ 12 │ 16 │ 20 │ 24 │ 28 │ 32 │ 36 │ 40 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 5 │ 10 │ 15 │ 20 │ 25 │ 30 │ 35 │ 40 │ 45 │ 50 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 6 │ 12 │ 18 │ 24 │ 30 │ 36 │ 42 │ 48 │ 54 │ 60 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 7 │ 14 │ 21 │ 28 │ 35 │ 42 │ 49 │ 56 │ 63 │ 70 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 8 │ 16 │ 24 │ 32 │ 40 │ 48 │ 56 │ 64 │ 72 │ 80 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 9 │ 18 │ 27 │ 36 │ 45 │ 54 │ 63 │ 72 │ 81 │ 90 │ +├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ +│ 10 │ 20 │ 30 │ 40 │ 50 │ 60 │ 70 │ 80 │ 90 │ 100 │ +└────┴────┴────┴────┴────┴────┴────┴────┴────┴─────┘ +``` + +## merge_duplicates + +``` +┌────────────┬─────────┬────────┐ +│ db │ table │ total │ +├────────────┼─────────┼────────┤ +│ database_1 │ table_1 │ 10712 │ +│ ├─────────┼────────┤ +│ │ table_2 │ 57 │ +│ ├─────────┤ │ +│ │ table_3 │ │ +├────────────┼─────────┼────────┤ +│ database_2 │ table_1 │ 72 │ +│ ├─────────┼────────┤ +│ │ table_2 │ 75 │ +├────────────┼─────────┼────────┤ +│ database_3 │ table_1 │ 20 │ +│ ├─────────┼────────┤ +│ │ table_2 │ 21339 │ +│ ├─────────┼────────┤ +│ │ table_3 │ 141723 │ +└────────────┴─────────┴────────┘ +``` + +## merge_duplicates_2 + +``` +╭───────────┬───────────────────────────────────────────────────────────────────────────╮ +│ │ 0 1 2 3 4 5 6 7 │ +├───────────┼───────────────────────────────────────────────────────────────────────────┤ +│ db │ database_1 database_2 database_3 │ +│ origin_db │ database_1 database_3 │ +│ table │ table_1 table_2 table_3 table_1 table_2 table_1 table_2 table_3 │ +│ total │ 10712 57 72 75 20 21339 141723 │ +╰───────────┴───────────────────────────────────────────────────────────────────────────╯ +``` + +## settings_list + +``` ++----------------------+-----------------+--------------------+ +| name | first_release | developer | ++----------------------+-----------------+--------------------+ +| Sublime Text 3 | 2008 | Sublime HQ | ++----------------------+-----------------+--------------------+ +| Visual Studio Code | 2015 | Microsoft | ++----------------------+-----------------+--------------------+ +| Notepad++ | 2003 | Don Ho | ++----------------------+-----------------+--------------------+ +| GNU Emacs | 1984 | Richard Stallman | ++----------------------+-----------------+--------------------+ +| Neovim | 2015 | Vim community | ++----------------------+-----------------+--------------------+ +``` + +## split + +``` +┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ +│ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │ +└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ +│ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ │ a │ b │ │ a │ b │ │ a │ y │ b │ z │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ ├───┼───┤ ├───┼───┤ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ +│ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ │ c │ d │ │ m │ n │ │ m │ │ n │ │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ +├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ ├───┼───┤ ├───┼───┤ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ +│ y │ z │ │ │ │ │ │ │ │ │ │ │ │ e │ f │ │ y │ z │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ +└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ├───┼───┤ ├───┼───┤ │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ y │ z │ + │ g │ h │ │ c │ d │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ + ├───┼───┤ ├───┼───┤ │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ │ │ + │ i │ j │ │ o │ p │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ + ├───┼───┤ ├───┼───┤ + │ k │ l │ │ e │ f │ + ├───┼───┤ ├───┼───┤ + │ m │ n │ │ q │ r │ + ├───┼───┤ ├───┼───┤ + │ o │ p │ │ g │ h │ + ├───┼───┤ ├───┼───┤ + │ q │ r │ │ s │ t │ + ├───┼───┤ ├───┼───┤ + │ s │ t │ │ i │ j │ + ├───┼───┤ ├───┼───┤ + │ u │ v │ │ u │ v │ + ├───┼───┤ ├───┼───┤ + │ w │ x │ │ k │ l │ + ├───┼───┤ ├───┼───┤ + │ y │ z │ │ w │ x │ + └───┴───┘ └───┴───┘ +```
\ No newline at end of file diff --git a/vendor/tabled/examples/alphabet.rs b/vendor/tabled/examples/alphabet.rs new file mode 100644 index 000000000..5cc8250ad --- /dev/null +++ b/vendor/tabled/examples/alphabet.rs @@ -0,0 +1,13 @@ +//! This example demonstrates instantiating a [`Table`] from an [`IntoIterator`] compliant object. +//! +//! * Note how [`Range`] [expression syntax](https://doc.rust-lang.org/reference/expressions/range-expr.html) +//! is used to idiomatically represent the English alphabet. + +use std::iter::FromIterator; + +use tabled::Table; + +fn main() { + let table = Table::from_iter(['a'..='z']); + println!("{table}"); +} diff --git a/vendor/tabled/examples/border_text.rs b/vendor/tabled/examples/border_text.rs new file mode 100644 index 000000000..bbdb7673e --- /dev/null +++ b/vendor/tabled/examples/border_text.rs @@ -0,0 +1,44 @@ +//! This example demonstrates inserting text into the borders +//! of a [`Table`] with [`BorderText`]; a powerful labeling tool. +//! +//! * [`BorderText`] currently supports: +//! * Horizontal border placement +//! * Placement starting column offset +//! * Text colorization +//! +//! * Note how the flexibility of [`Style`] is utilized +//! to remove horizontal borders from the table entirely, +//! and then granularly reinserts one for a highly customized +//! visualization. +//! +//! * Note how the [`Rows`] utility object is used to idiomatically +//! reference the first and last rows of a [`Table`] without writing +//! the necessary logic by hand. +//! +//! * 🚀 Combining several easy-to-use tools, +//! to create unique data representations is what makes [`tabled`] great! + +use tabled::{ + settings::{ + object::Rows, + style::{BorderText, HorizontalLine, Style}, + }, + Table, +}; + +fn main() { + let data = [[5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]; + + let table = Table::new(data) + .with( + Style::modern() + .remove_horizontal() + .horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())]), + ) + .with(BorderText::new(" Numbers ").horizontal(Rows::first())) + .with(BorderText::new(" More numbers ").horizontal(1)) + .with(BorderText::new(" end. ").horizontal(Rows::last())) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/builder.rs b/vendor/tabled/examples/builder.rs new file mode 100644 index 000000000..6baf29c24 --- /dev/null +++ b/vendor/tabled/examples/builder.rs @@ -0,0 +1,38 @@ +//! This example demonstrates an alternative method for creating a [`Table`]. +//! [`Builder`] is an efficient implementation of the [builder design pattern](https://en.wikipedia.org/wiki/Builder_pattern). +//! +//! > The intent of the Builder design pattern is to separate the construction of a complex object from its representation. +//! > -- <cite>Wikipedia</cite> +//! +//! * Note how [Builder] can be used to define a table's shape manually +//! and can be populated through iteration if it is mutable. This flexibility +//! is useful when you don't have direct control over the datasets you intend to [table](tabled). + +use tabled::{ + builder::Builder, + settings::{object::Rows, Modify, Panel, Style, Width}, +}; + +fn main() { + let message = r#"The terms "the ocean" or "the sea" used without specification refer to the interconnected body of salt water covering the majority of the Earth's surface"#; + let link = r#"https://en.wikipedia.org/wiki/Ocean"#; + + let oceans = ["Atlantic", "Pacific", "Indian", "Southern", "Arctic"]; + + let mut builder = Builder::default(); + builder.set_header(["#", "Ocean"]); + for (i, ocean) in oceans.iter().enumerate() { + builder.push_record([i.to_string(), ocean.to_string()]); + } + + let table = builder + .build() + .with(Panel::header(message)) + .with(Panel::header(link)) + .with(Panel::horizontal(2, "=".repeat(link.len()))) + .with(Modify::new(Rows::single(1)).with(Width::wrap(link.len()))) + .with(Style::markdown()) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/builder_index.rs b/vendor/tabled/examples/builder_index.rs new file mode 100644 index 000000000..9a53eb566 --- /dev/null +++ b/vendor/tabled/examples/builder_index.rs @@ -0,0 +1,50 @@ +//! This example demonstrates evolving the standard [`Builder`] to an [`IndexBuilder`], +//! and then manipulating the constructing table with a newly prepended index column. +//! +//! * An [`IndexBuilder`] is capable of several useful manipulations, including: +//! * Giving the new index column a name +//! * Transposing the index column around a table +//! * Choosing a location for the new index column besides 0; the default +//! +//! * Note that like with any builder pattern the [`IndexBuilder::build()`] function +//! is necessary to produce a displayable [`Table`]. + +use tabled::{settings::Style, Table, Tabled}; + +#[derive(Tabled)] +struct Distribution { + name: &'static str, + based_on: &'static str, + is_active: bool, + is_cool: bool, +} + +impl Distribution { + fn new(name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool) -> Self { + Self { + name, + based_on, + is_active, + is_cool, + } + } +} + +fn main() { + let data = [ + Distribution::new("Manjaro", "Arch", true, true), + Distribution::new("Arch", "None", true, true), + Distribution::new("Debian", "None", true, true), + ]; + + let mut table = Table::builder(data) + .index() + .column(0) + .name(None) + .transpose() + .build(); + + table.with(Style::modern()); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/chess.rs b/vendor/tabled/examples/chess.rs new file mode 100644 index 000000000..db83218c8 --- /dev/null +++ b/vendor/tabled/examples/chess.rs @@ -0,0 +1,37 @@ +//! This example demonstrates using the [`Color`] [setting](tabled::settings) to +//! stylize text, backgrounds, and borders. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Note how [`Format::content()`] is used to break out [`CellOption`] +//! specifications. This is helpful for organizing extensive [`Table`] configurations. + +use std::iter::FromIterator; + +use tabled::{ + builder::Builder, + settings::{style::Style, themes::Colorization, Color}, +}; + +fn main() { + let board = [ + ["♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"], + ["♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"], + [" ", " ", " ", " ", " ", " ", " ", " "], + [" ", " ", " ", " ", " ", " ", " ", " "], + [" ", " ", " ", " ", " ", " ", " ", " "], + [" ", " ", " ", " ", " ", " ", " ", " "], + ["♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"], + ["♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"], + ]; + + let color_white = Color::BG_WHITE | Color::FG_BLACK; + let color_black = Color::FG_WHITE | Color::BG_BLACK; + + let mut table = Builder::from_iter(board).build(); + table + .with(Style::empty()) + .with(Colorization::chess(color_white, color_black)); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/col_row_macros.rs b/vendor/tabled/examples/col_row_macros.rs new file mode 100644 index 000000000..1a676eb67 --- /dev/null +++ b/vendor/tabled/examples/col_row_macros.rs @@ -0,0 +1,62 @@ +//! This example demonstrates using the [`col!`] and [`row!`] macros to easily +//! organize multiple tables together into a single, new [`Table`] display. +//! +//! * 🚩 This example requires the `macros` feature. +//! +//! * Note how both macros can be used in combination to layer +//! several table arrangements together. +//! +//! * Note how [`col!`] and [`row!`] support idiomatic argument duplication +//! with the familiar `[T; N]` syntax. + +use tabled::{ + col, row, + settings::{Alignment, Style}, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Person { + name: String, + age: u8, + is_validated: bool, +} + +impl Person { + fn new(name: &str, age: u8, is_validated: bool) -> Self { + Self { + name: name.into(), + age, + is_validated, + } + } +} + +fn main() { + let validated = [Person::new("Sam", 31, true), Person::new("Sarah", 26, true)]; + + let not_validated = [ + Person::new("Jack Black", 51, false), + Person::new("Michelle Goldstein", 44, true), + ]; + + let unsure = [ + Person::new("Jon Doe", 255, false), + Person::new("Mark Nelson", 13, true), + Person::new("Terminal Monitor", 0, false), + Person::new("Adam Blend", 17, true), + ]; + + let table_a = Table::new(&validated).with(Style::ascii()).to_string(); + let table_b = Table::new(¬_validated).with(Style::modern()).to_string(); + let table_c = Table::new(&unsure).with(Style::ascii_rounded()).to_string(); + + let row_table = row![table_c, table_b]; + + let col_table = col![table_c; 3]; + + let mut row_col_table = col![row![table_a, table_b].with(Style::empty()), table_c]; + row_col_table.with(Alignment::center()); + + println!("{row_table}\n{col_table}\n{row_col_table}",); +} diff --git a/vendor/tabled/examples/color.rs b/vendor/tabled/examples/color.rs new file mode 100644 index 000000000..b7064b306 --- /dev/null +++ b/vendor/tabled/examples/color.rs @@ -0,0 +1,74 @@ +//! This example demonstrates using the [`Color`] [setting](tabled::settings) to +//! stylize text, backgrounds, and borders. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Note how [`Format::content()`] is used to break out [`CellOption`] +//! specifications. This is helpful for organizing extensive [`Table`] configurations. + +use std::convert::TryFrom; + +use owo_colors::OwoColorize; + +use tabled::{ + settings::{ + object::{Columns, Rows}, + style::{BorderColor, Style}, + Color, Format, Modify, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Bsd { + distribution: &'static str, + year_of_first_release: usize, + is_active: bool, +} + +impl Bsd { + fn new(distribution: &'static str, year_of_first_release: usize, is_active: bool) -> Self { + Self { + distribution, + year_of_first_release, + is_active, + } + } +} + +fn main() { + let data = vec![ + Bsd::new("BSD", 1978, false), + Bsd::new("SunOS", 1982, false), + Bsd::new("NetBSD", 1993, true), + Bsd::new("FreeBSD", 1993, true), + Bsd::new("OpenBSD", 1995, true), + ]; + + let red = Format::content(|s| s.red().on_bright_white().to_string()); + let blue = Format::content(|s| s.blue().to_string()); + let green = Format::content(|s| s.green().to_string()); + + let color_red = Color::try_from(' '.red().to_string()).unwrap(); + let color_purple = Color::try_from(' '.purple().to_string()).unwrap(); + + let yellow_color = Color::try_from(' '.yellow().to_string()).unwrap(); + + let first_row_style = Modify::new(Rows::first()).with( + BorderColor::default() + .bottom(color_red) + .corner_bottom_left(color_purple.clone()) + .corner_bottom_right(color_purple), + ); + + let mut table = Table::new(data); + table + .with(Style::psql()) + .with(yellow_color) + .with(first_row_style) + .with(Modify::new(Columns::single(0)).with(red)) + .with(Modify::new(Columns::single(1)).with(green)) + .with(Modify::new(Columns::single(2)).with(blue)); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/colored_borders.rs b/vendor/tabled/examples/colored_borders.rs new file mode 100644 index 000000000..0bbdb4ac4 --- /dev/null +++ b/vendor/tabled/examples/colored_borders.rs @@ -0,0 +1,65 @@ +//! This example demonstrates using the [`RawStyle`] [setting](tabled::settings) to +//! to granularly specify border colors. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Note how [`Color`] containts several helpful, const values covering +//! a basic selection of foreground and background colors. [`Color`] also +//! supports custom colors with [`Color::new()`]. + +use tabled::{ + settings::{ + style::{RawStyle, Style}, + Color, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct CodeEditor { + name: &'static str, + first_release: &'static str, + developer: &'static str, +} + +impl CodeEditor { + fn new(name: &'static str, first_release: &'static str, developer: &'static str) -> Self { + Self { + name, + first_release, + developer, + } + } +} + +fn main() { + let mut style = RawStyle::from(Style::extended()); + style + .set_color_top(Color::FG_RED) + .set_color_bottom(Color::FG_CYAN) + .set_color_left(Color::FG_BLUE) + .set_color_right(Color::FG_GREEN) + .set_color_corner_top_left(Color::FG_BLUE) + .set_color_corner_top_right(Color::FG_RED) + .set_color_corner_bottom_left(Color::FG_CYAN) + .set_color_corner_bottom_right(Color::FG_GREEN) + .set_color_intersection_bottom(Color::FG_CYAN) + .set_color_intersection_top(Color::FG_RED) + .set_color_intersection_right(Color::FG_GREEN) + .set_color_intersection_left(Color::FG_BLUE) + .set_color_intersection(Color::FG_MAGENTA) + .set_color_horizontal(Color::FG_MAGENTA) + .set_color_vertical(Color::FG_MAGENTA); + + let data = [ + CodeEditor::new("Sublime Text 3", "2008", "Sublime HQ"), + CodeEditor::new("Visual Studio Code", "2015", "Microsoft"), + CodeEditor::new("Notepad++", "2003", "Don Ho"), + CodeEditor::new("GNU Emacs", "1984", "Richard Stallman"), + CodeEditor::new("Neovim", "2015", "Vim community"), + ]; + + let table = Table::new(data).with(style).to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/colored_padding.rs b/vendor/tabled/examples/colored_padding.rs new file mode 100644 index 000000000..96a3a717f --- /dev/null +++ b/vendor/tabled/examples/colored_padding.rs @@ -0,0 +1,173 @@ +//! This example demonstrates using the [`Padding::colorize()`] function in several ways +//! to give a [`Table`] display a vibrant asthetic. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Note how the [`Color`] [setting](tabled::settings) is used to simplify creating +//! reusable themes for text, backgrounds, padded whitespace, and borders. +//! +//! * Note how a unique color can be set for each direction. + +use std::convert::TryFrom; + +use owo_colors::OwoColorize; + +use tabled::{ + grid::{ + config::{ColoredConfig, Entity}, + dimension::SpannedGridDimension, + records::{ + vec_records::{Cell, VecRecords}, + ExactRecords, PeekableRecords, Records, + }, + util::string::string_width_multiline, + }, + settings::{ + object::{Columns, Object, Rows, Segment}, + Alignment, CellOption, Color, Format, Margin, Modify, Padding, Style, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +#[tabled(rename_all = "PascalCase")] +struct Fundamental { + quantity: &'static str, + symbol: &'static str, + value: &'static str, + unit: &'static str, +} + +impl Fundamental { + fn new( + quantity: &'static str, + symbol: &'static str, + value: &'static str, + unit: &'static str, + ) -> Self { + Self { + quantity, + symbol, + value, + unit, + } + } +} + +fn main() { + // data source: https://www.britannica.com/science/physical-constant + let data = [ + Fundamental::new( + "constant of gravitation", + "G", + "6.67384 × 10⁻¹¹", + "cubic metre per second squared per kilogram", + ), + Fundamental::new( + "speed of light (in a vacuum)", + "c", + "2.99792458 × 10⁻⁸", + "metres per second", + ), + Fundamental::new( + "Planck's constant", + "h", + "6.626070040 × 10⁻³⁴", + "joule second", + ), + Fundamental::new( + "Boltzmann constant", + "k", + "1.38064852 × 10⁻²³", + "joule per kelvin", + ), + Fundamental::new( + "Faraday constant", + "F", + "9.648533289 × 10⁴", + "coulombs per mole", + ), + ]; + + let pane_color = Color::try_from(' '.bg_rgb::<220, 220, 220>().to_string()).unwrap(); + let border_color = Color::try_from(' '.bg_rgb::<200, 200, 220>().bold().to_string()).unwrap(); + let data_color = Color::try_from(' '.bg_rgb::<200, 200, 220>().to_string()).unwrap(); + + let header_settings = Modify::new(Rows::first()) + .with(Padding::new(1, 1, 2, 2).colorize( + Color::BG_GREEN, + Color::BG_YELLOW, + Color::BG_MAGENTA, + Color::BG_CYAN, + )) + .with(MakeMaxPadding) + .with(Format::content(|s| s.on_black().white().to_string())); + + let data_settings = Modify::new(Rows::first().inverse()) + .with(Alignment::left()) + .with(MakeMaxPadding) + .with(Padding::new(1, 1, 0, 0).colorize( + Color::default(), + Color::default(), + data_color.clone(), + data_color.clone(), + )); + + let symbol_settings = Modify::new(Columns::single(1).not(Rows::first())) + .with(Format::content(|s| s.bold().to_string())); + + let unit_settings = Modify::new(Columns::single(3).not(Rows::first())) + .with(Format::content(|s| s.italic().to_string())); + + let table = Table::new(data) + .with(Style::rounded()) + .with(Margin::new(1, 2, 1, 1).colorize( + pane_color.clone(), + pane_color.clone(), + pane_color.clone(), + pane_color, + )) + .with(border_color) + .with(Modify::new(Segment::all()).with(data_color)) + .with(header_settings) + .with(data_settings) + .with(symbol_settings) + .with(unit_settings) + .to_string(); + + println!("\n\n{table}\n\n"); +} + +#[derive(Debug, Clone)] +struct MakeMaxPadding; + +impl<T> CellOption<VecRecords<T>, ColoredConfig> for MakeMaxPadding +where + T: Cell + AsRef<str>, +{ + fn change(self, records: &mut VecRecords<T>, cfg: &mut ColoredConfig, entity: Entity) { + let widths = SpannedGridDimension::width(&*records, cfg); + + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for (row, col) in entity.iter(count_rows, count_cols) { + let column_width = widths[col]; + let text = records.get_text((row, col)); + let width = string_width_multiline(text); + + if width < column_width { + let available_width = column_width - width; + let left = available_width / 2; + let right = available_width - left; + + let pos = (row, col).into(); + let mut pad = cfg.get_padding(pos); + pad.left.size = left; + pad.right.size = right; + + cfg.set_padding(pos, pad); + } + } + } +} diff --git a/vendor/tabled/examples/colorization.rs b/vendor/tabled/examples/colorization.rs new file mode 100644 index 000000000..a68ca93e8 --- /dev/null +++ b/vendor/tabled/examples/colorization.rs @@ -0,0 +1,69 @@ +//! This example demonstrates using the [`Color`] [setting](tabled::settings) to +//! stylize text, backgrounds, and borders. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Note how [`Format::content()`] is used to break out [`CellOption`] +//! specifications. This is helpful for organizing extensive [`Table`] configurations. + +use tabled::{ + builder::Builder, + settings::{object::Rows, style::Style, themes::Colorization, Color, Concat}, + Table, Tabled, +}; + +#[derive(Tabled)] +#[tabled(rename_all = "UPPERCASE")] +struct Employee { + id: usize, + #[tabled(rename = "FIRST NAME")] + first_name: String, + #[tabled(rename = "LAST NAME")] + last_name: String, + salary: usize, + comment: String, +} + +impl Employee { + fn new(id: usize, first_name: &str, last_name: &str, salary: usize, comment: &str) -> Self { + Self { + id, + salary, + first_name: first_name.to_string(), + last_name: last_name.to_string(), + comment: comment.to_string(), + } + } +} + +fn main() { + let data = vec![ + Employee::new(1, "Arya", "Stark", 3000, ""), + Employee::new(20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"), + Employee::new(300, "Tyrion", "Lannister", 5000, ""), + ]; + + let total = data.iter().map(|e| e.salary).sum::<usize>(); + let total_row = Builder::from(vec![vec![ + String::from(""), + String::from(""), + String::from("TOTAL"), + total.to_string(), + ]]) + .build(); + + let color_data_primary = Color::BG_WHITE | Color::FG_BLACK; + let color_data_second = Color::BG_BRIGHT_WHITE | Color::FG_BLACK; + let color_head = Color::BOLD | Color::BG_CYAN | Color::FG_BLACK; + let color_footer = Color::BOLD | Color::BG_BLUE | Color::FG_BLACK; + + let mut table = Table::new(data); + table + .with(Concat::vertical(total_row)) + .with(Style::empty()) + .with(Colorization::rows([color_data_primary, color_data_second])) + .with(Colorization::exact([color_head], Rows::first())) + .with(Colorization::exact([color_footer], Rows::last())); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/column_names.rs b/vendor/tabled/examples/column_names.rs new file mode 100644 index 000000000..0388e6264 --- /dev/null +++ b/vendor/tabled/examples/column_names.rs @@ -0,0 +1,63 @@ +//! This example demonstrates how to set column names on a top horizontal line. +//! +//! It sets a `clickhouse` like table style (first seen on). + +use std::iter::repeat; + +use tabled::{ + grid::config::AlignmentHorizontal, + settings::{themes::ColumnNames, Color, Style}, + Table, Tabled, +}; + +#[derive(Debug, Tabled)] +struct Function { + declaration: String, + name: String, + return_type: String, +} + +impl Function { + fn new(decl: &str, name: &str, ret_type: &str) -> Self { + Self { + declaration: decl.to_string(), + name: name.to_string(), + return_type: ret_type.to_string(), + } + } +} + +fn main() { + let data = vec![ + Function::new( + "struct stack *stack_create(int)", + "stack_create", + "struct stack *", + ), + Function::new( + "void stack_destroy(struct stack *)", + "stack_destroy", + "void", + ), + Function::new( + "int stack_put(struct stack *, vm_offset_t)", + "stack_put", + "int", + ), + Function::new( + "void stack_copy(const struct stack *, struct stack *)", + "stack_copy", + "void", + ), + ]; + + let mut table = Table::new(data); + + table.with(Style::modern().remove_horizontal()).with( + ColumnNames::default() + .set_colors(repeat(Color::BOLD | Color::BG_BLUE | Color::FG_WHITE).take(3)) + .set_alignment(AlignmentHorizontal::Center), + ); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/compact_table.rs b/vendor/tabled/examples/compact_table.rs new file mode 100644 index 000000000..3a02d448d --- /dev/null +++ b/vendor/tabled/examples/compact_table.rs @@ -0,0 +1,23 @@ +//! This example demonstrates creating a `new()` [`CompactTable`] with +//! manual specifications for column count, column widths, and border styling. +//! +//! * [`CompactTable`] is a [`Table`] alternative that trades off reduced +//! flexibility for improved performance. + +use tabled::{settings::Style, tables::CompactTable}; + +fn main() { + let data = [ + ["Debian", "", "true"], + ["Arch", "", "true"], + ["Manjaro", "Arch", "true"], + ]; + + let _table = CompactTable::new(data) + .columns(3) + .width([7, 5, 5]) + .with(Style::markdown()); + + #[cfg(feature = "std")] + println!("{}", _table.to_string()); +} diff --git a/vendor/tabled/examples/compact_table_2.rs b/vendor/tabled/examples/compact_table_2.rs new file mode 100644 index 000000000..5f231ccd8 --- /dev/null +++ b/vendor/tabled/examples/compact_table_2.rs @@ -0,0 +1,20 @@ +//! This example demonstrates creating a [`CompactTable`] `from()` a +//! multidimensional array. +//! +//! * Note how [`CompactTable::from()`] inherits the lengths of the nested arrays +//! as typed definitions through [const generics](https://practice.rs/generics-traits/const-generics.html). + +use tabled::{settings::Style, tables::CompactTable}; + +fn main() { + let data = [ + ["Debian", "1.1.1.1", "true"], + ["Arch", "127.1.1.1", "true"], + ["Manjaro", "Arch", "true"], + ]; + + let _table = CompactTable::from(data).with(Style::psql()); + + #[cfg(feature = "std")] + println!("{}", _table.to_string()); +} diff --git a/vendor/tabled/examples/compact_table_3.rs b/vendor/tabled/examples/compact_table_3.rs new file mode 100644 index 000000000..5c7df6d5e --- /dev/null +++ b/vendor/tabled/examples/compact_table_3.rs @@ -0,0 +1,19 @@ +//! This example demonstrates how [`CompactTable`] is limited to single +//! line rows. +//! +//! * Note how the multiline data is accepted, but then truncated in the display. + +use tabled::{settings::Style, tables::CompactTable}; + +fn main() { + let data = [ + ["De\nbi\nan", "1.1.1.1", "true"], + ["Arch", "127.1.1.1", "true"], + ["Manjaro", "A\nr\nc\nh", "true"], + ]; + + let _table = CompactTable::from(data).with(Style::psql()); + + #[cfg(feature = "std")] + println!("{}", _table.to_string()); +} diff --git a/vendor/tabled/examples/concat.rs b/vendor/tabled/examples/concat.rs new file mode 100644 index 000000000..09afeed7b --- /dev/null +++ b/vendor/tabled/examples/concat.rs @@ -0,0 +1,58 @@ +//! This example demonstrates using the [`Concat`] [`TableOption`] to concatenate +//! [`tables`](Table) together. +//! +//! * [`Concat`] supports appending tables vertically and horizontally. +//! +//! * Note how the base tables style settings take take precedence over the appended table. +//! If the two tables are of unequal shape, additional blank cells are added as needed. + +use tabled::{ + settings::{object::Segment, Alignment, Concat, Modify, Style}, + Table, Tabled, +}; + +#[derive(Debug, Tabled)] +struct Weather { + temperature_c: f64, + wind_ms: f64, +} + +#[derive(Debug, Tabled)] +struct Location( + #[tabled(rename = "latitude")] f64, + #[tabled(rename = "longitude")] f64, +); + +fn main() { + let weather_data = [ + Weather { + temperature_c: 1.0, + wind_ms: 3.0, + }, + Weather { + temperature_c: -20.0, + wind_ms: 30.0, + }, + Weather { + temperature_c: 40.0, + wind_ms: 100.0, + }, + ]; + + let location_data = [ + Location(111.111, 333.333), + Location(5.111, 7282.1), + Location(0.0, 0.0), + Location(0.0, 0.0), + ]; + + let location_table = Table::new(location_data); + + let mut weather_table = Table::new(weather_data); + weather_table + .with(Concat::horizontal(location_table)) + .with(Style::empty()) + .with(Modify::new(Segment::all()).with(Alignment::left())); + + println!("{weather_table}"); +} diff --git a/vendor/tabled/examples/custom_style.rs b/vendor/tabled/examples/custom_style.rs new file mode 100644 index 000000000..e6f0848cb --- /dev/null +++ b/vendor/tabled/examples/custom_style.rs @@ -0,0 +1,54 @@ +//! This example demonstrates customizing one of the [`tabled`] default [styles](Style) +//! to create a unique [`Table`] display. +//! +//! * Note that all predesigned styles can be configured completely. +//! Styles can also be created from scratch! +//! +//! * Note that adding and removing borders with a [`Style`] theme doesn't affect the +//! number of functional columns and rows. + +use tabled::{ + settings::{ + style::{HorizontalLine, Style, VerticalLine}, + Alignment, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct CodeEditor { + name: &'static str, + first_release: &'static str, + developer: &'static str, +} + +impl CodeEditor { + fn new(name: &'static str, first_release: &'static str, developer: &'static str) -> Self { + Self { + name, + first_release, + developer, + } + } +} + +fn main() { + let data = [ + CodeEditor::new("Sublime Text 3", "2008", "Sublime HQ"), + CodeEditor::new("Visual Studio Code", "2015", "Microsoft"), + CodeEditor::new("Notepad++", "2003", "Don Ho"), + CodeEditor::new("GNU Emacs", "1984", "Richard Stallman"), + CodeEditor::new("Neovim", "2015", "Vim community"), + ]; + + let theme = Style::modern() + .remove_horizontal() + .remove_vertical() + .horizontals([HorizontalLine::new(1, Style::modern().get_horizontal()).intersection(None)]) + .verticals([VerticalLine::new(1, Style::modern().get_vertical())]); + + let mut table = Table::new(data); + table.with(theme).with(Alignment::left()); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/display_with.rs b/vendor/tabled/examples/derive/display_with.rs new file mode 100644 index 000000000..9dfc87a47 --- /dev/null +++ b/vendor/tabled/examples/derive/display_with.rs @@ -0,0 +1,65 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`display_with`] to seamlessly augment field representations in a [`Table`] display. +//! +//! * [`display_with`] functions act as transformers during [`Table`] instantiation. +//! +//! * Note how [`display_with`] works with [std] and custom functions alike. +//! +//! * [`display_with`] attributes can be constructed in two ways (shown below). +//! +//! * Attribute arguments can be directly overridden with static values, effectively ignoring the +//! augmented fields natural value entirely. Even an entire object can be passed as context with `self`. + +use std::borrow::Cow; + +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +#[tabled(rename_all = "camelCase")] +struct Country { + name: &'static str, + capital_city: &'static str, + #[tabled(display_with("display_perimeter", self))] + surface_area_km2: f32, + #[tabled(display_with = "str::to_lowercase")] + national_currency: &'static str, + national_currency_short: &'static str, +} + +fn display_perimeter(country: &Country) -> Cow<'_, str> { + if country.surface_area_km2 > 1_000_000.0 { + "Very Big Land".into() + } else { + "Big Land".into() + } +} + +impl Country { + fn new( + name: &'static str, + national_currency: &'static str, + national_currency_short: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + ) -> Self { + Self { + name, + national_currency, + national_currency_short, + capital_city, + surface_area_km2, + } + } +} + +fn main() { + let data = [ + Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), + Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), + Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/inline.rs b/vendor/tabled/examples/derive/inline.rs new file mode 100644 index 000000000..2ce07816f --- /dev/null +++ b/vendor/tabled/examples/derive/inline.rs @@ -0,0 +1,55 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`inline`] to expand struct fields to individual columns in a [`Table`] display. +//! +//! * Note that without inlining a struct or enum field, those objects +//! must implement the [`Display`] trait as they will be represented in +//! a single column with the value of their [`ToString`] output. + +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +struct Country { + name: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + #[tabled(inline)] + currency: Currency, +} + +#[derive(Tabled)] +struct Currency { + str: &'static str, + short: &'static str, +} + +impl Country { + fn new( + name: &'static str, + national_currency: &'static str, + national_currency_short: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + ) -> Self { + Self { + name, + capital_city, + surface_area_km2, + currency: Currency { + str: national_currency, + short: national_currency_short, + }, + } + } +} + +fn main() { + let data = [ + Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), + Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), + Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/inline_enum.rs b/vendor/tabled/examples/derive/inline_enum.rs new file mode 100644 index 000000000..707d5bb2c --- /dev/null +++ b/vendor/tabled/examples/derive/inline_enum.rs @@ -0,0 +1,50 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`inline`] to expand enum fields to individual columns in a [`Table`] display. +//! +//! * Note how the optional [`inline`] argument is used to apply prefixes +//! to decomposed column headers. This is helpful for organizing tables +//! with repetative fields that would normally result in confusing headers. +//! +//! * Note that without inlining a struct or enum field, those objects +//! must implement the [`Display`] trait as they will be represented in +//! a single column with the value of their [`ToString`] output. + +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +enum Contact { + #[tabled(inline("telegram::"))] + Telegram { + username: &'static str, + #[tabled(inline("telegram::"))] + number: Number, + }, + #[tabled(inline)] + Local(#[tabled(inline("local::"))] Number), +} + +#[derive(Tabled)] +struct Number { + number: &'static str, + code: usize, +} + +impl Number { + fn new(number: &'static str, code: usize) -> Self { + Self { number, code } + } +} + +fn main() { + let data = [ + Contact::Local(Number::new("654321", 123)), + Contact::Telegram { + username: "no2Presley", + number: Number::new("123456", 123), + }, + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/order.rs b/vendor/tabled/examples/derive/order.rs new file mode 100644 index 000000000..44e036fad --- /dev/null +++ b/vendor/tabled/examples/derive/order.rs @@ -0,0 +1,48 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`order`] to relocate fields to specified indexes in a [`Table`] display. +//! +//! * By default, [`Table`] columns are shown in the same ordered they are +//! defined in the deriving struct/enum definition. + +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +struct Country { + name: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + #[tabled(order = 1)] + national_currency: &'static str, + #[tabled(order = 2)] + national_currency_short: &'static str, +} + +impl Country { + fn new( + name: &'static str, + national_currency: &'static str, + national_currency_short: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + ) -> Self { + Self { + name, + national_currency, + national_currency_short, + capital_city, + surface_area_km2, + } + } +} + +fn main() { + let data = [ + Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), + Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), + Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/rename.rs b/vendor/tabled/examples/derive/rename.rs new file mode 100644 index 000000000..709411948 --- /dev/null +++ b/vendor/tabled/examples/derive/rename.rs @@ -0,0 +1,45 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`rename`] to alias specific fields in a [`Table`] display. + +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +struct Country { + name: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + #[tabled(rename = "Currency")] + national_currency: &'static str, + #[tabled(rename = "Currency-ISO")] + national_currency_short: &'static str, +} + +impl Country { + fn new( + name: &'static str, + national_currency: &'static str, + national_currency_short: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + ) -> Self { + Self { + name, + national_currency, + national_currency_short, + capital_city, + surface_area_km2, + } + } +} + +fn main() { + let data = [ + Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), + Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), + Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/rename_all.rs b/vendor/tabled/examples/derive/rename_all.rs new file mode 100644 index 000000000..cd408e5ba --- /dev/null +++ b/vendor/tabled/examples/derive/rename_all.rs @@ -0,0 +1,56 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`rename_all`] to apply table-wide header formatting in a [`Table`] display. +//! +//! * Supported formatting rules include: +//! * 'camelCase' +//! * 'kabab-case' +//! * 'PascalCase' +//! * 'SCREAMING_SNAKE_CASE' +//! * 'snake_case' +//! * 'lowercase' +//! * 'UPPERCASE' +//! * 'verbatim' + +use tabled::{Table, Tabled}; + +#[derive(Tabled)] +#[tabled(rename_all = "camelCase")] +struct Country { + name: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + #[tabled(rename_all = "kebab-case")] + national_currency: &'static str, + #[tabled(rename_all = "kebab-case")] + national_currency_short: &'static str, +} + +impl Country { + fn new( + name: &'static str, + national_currency: &'static str, + national_currency_short: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + ) -> Self { + Self { + name, + national_currency, + national_currency_short, + capital_city, + surface_area_km2, + } + } +} + +fn main() { + let data = [ + Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), + Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), + Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/derive/skip.rs b/vendor/tabled/examples/derive/skip.rs new file mode 100644 index 000000000..86f7a9ba2 --- /dev/null +++ b/vendor/tabled/examples/derive/skip.rs @@ -0,0 +1,49 @@ +//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) +//! [`skip`] to omit specific fields from becoming columns in a [`Table`] display. +//! +//! * Note how [`skip`] annoys [clippy](https://doc.rust-lang.org/clippy/) with `dead_code` +//! warnings. This can be addressed with compiler overrides like `#[allow(dead_code)]`. + +use tabled::{Table, Tabled}; + +#[allow(dead_code)] +#[derive(Tabled)] +struct Country { + name: &'static str, + capital_city: &'static str, + #[tabled(skip)] + surface_area_km2: f32, + national_currency: &'static str, + #[tabled(skip)] + national_currency_short: &'static str, +} + +impl Country { + fn new( + name: &'static str, + national_currency: &'static str, + national_currency_short: &'static str, + capital_city: &'static str, + surface_area_km2: f32, + ) -> Self { + Self { + name, + national_currency, + national_currency_short, + capital_city, + surface_area_km2, + } + } +} + +fn main() { + let data = [ + Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), + Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), + Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), + ]; + + let table = Table::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/disable.rs b/vendor/tabled/examples/disable.rs new file mode 100644 index 000000000..1f04de7d9 --- /dev/null +++ b/vendor/tabled/examples/disable.rs @@ -0,0 +1,49 @@ +//! This example demonstrates using the [`Disable`] [`TableOption`] to remove specific +//! cell data from a [`Table`] display. +//! +//! * ⚠️ Using [`Disable`] in combination with other [`Style`] customizations may yield unexpected results. +//! It is safest to use [`Disable`] last in a chain of alterations. + +use tabled::{ + settings::{ + locator::ByColumnName, + style::{Border, Style}, + Disable, Modify, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Distribution { + name: &'static str, + based_on: &'static str, + is_active: bool, + is_cool: bool, +} + +impl Distribution { + fn new(name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool) -> Self { + Self { + name, + based_on, + is_active, + is_cool, + } + } +} + +fn main() { + let data = [ + Distribution::new("Debian", "", true, true), + Distribution::new("Arch", "", true, true), + Distribution::new("Manjaro", "Arch", true, true), + ]; + + let mut table = Table::new(data); + table + .with(Style::markdown()) + .with(Disable::column(ByColumnName::new("is_active"))) + .with(Modify::new(ByColumnName::new("name")).with(Border::filled('#'))); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/extended_display.rs b/vendor/tabled/examples/extended_display.rs new file mode 100644 index 000000000..d31257881 --- /dev/null +++ b/vendor/tabled/examples/extended_display.rs @@ -0,0 +1,35 @@ +//! This example demonstrates using [ExtendedTable], a [Table] alternative with +//! limited flexibility but a greater emphasis on large data displays. + +use tabled::{tables::ExtendedTable, Tabled}; + +#[derive(Tabled)] +struct Distribution { + name: &'static str, + based_on: &'static str, + is_active: bool, + is_cool: bool, +} + +impl Distribution { + fn new(name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool) -> Self { + Self { + name, + based_on, + is_active, + is_cool, + } + } +} + +fn main() { + let data = vec![ + Distribution::new("Manjaro", "Arch", true, true), + Distribution::new("Arch", "", true, true), + Distribution::new("Debian", "", true, true), + ]; + + let table = ExtendedTable::new(data); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/extract.rs b/vendor/tabled/examples/extract.rs new file mode 100644 index 000000000..8d7d86434 --- /dev/null +++ b/vendor/tabled/examples/extract.rs @@ -0,0 +1,106 @@ +//! This example demonstrates using the [`Extract`] [`TableOption`] to +//! produce a subsection of a [`Table`]. +//! +//! * [`Extract`] can return a new [`Table`] with three functions: +//! * `rows()` | yields subset of the initial rows +//! * `columns()` | yields subset of the initial columns +//! * `segment()` | yields subsection of the initial table +//! +//! * Note how [`Extract`] methods accepts [`RangeBounds`] arguments, +//! making subset specifications concise. + +use std::fmt::{Display, Formatter}; + +use tabled::{ + settings::{ + object::{Columns, Rows}, + Alignment, Extract, Format, Modify, Style, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Album { + artist: &'static str, + name: &'static str, + released: &'static str, + level_of_greatness: LevelOfGreatness, +} + +impl Album { + fn new( + artist: &'static str, + name: &'static str, + released: &'static str, + level_of_greatness: LevelOfGreatness, + ) -> Self { + Self { + artist, + name, + released, + level_of_greatness, + } + } +} + +#[derive(Debug)] +enum LevelOfGreatness { + Supreme, + Outstanding, + Unparalleled, +} + +impl Display for LevelOfGreatness { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + std::fmt::Debug::fmt(&self, f) + } +} + +fn main() { + use LevelOfGreatness::*; + + let data = [ + Album::new( + "Pink Floyd", + "The Dark Side of the Moon", + "01 March 1973", + Unparalleled, + ), + Album::new("Fleetwood Mac", "Rumours", "04 February 1977", Outstanding), + Album::new( + "Led Zeppelin", + "Led Zeppelin IV", + "08 November 1971", + Supreme, + ), + ]; + + println!("Full"); + + let mut table = Table::new(data); + table + .with(Style::modern()) + .with(Modify::new(Rows::first()).with(Alignment::center())) + .with(Modify::new(Rows::new(1..)).with(Alignment::left())); + println!("{table}"); + + println!("Segment row: (1..=2) column: (1..)"); + + let table = table.with(Extract::segment(1..=2, 1..)); + println!("{table}"); + + println!("Refinished segment"); + + let highlight = Format::content(|s| { + if s == "Outstanding" { + format!("+{s}+") + } else { + s.to_string() + } + }); + + let table = table + .with(Style::modern()) + .with(Modify::new(Columns::new(1..)).with(highlight)); + println!("{table}"); +} diff --git a/vendor/tabled/examples/format.rs b/vendor/tabled/examples/format.rs new file mode 100644 index 000000000..7dc4bddb2 --- /dev/null +++ b/vendor/tabled/examples/format.rs @@ -0,0 +1,56 @@ +//! This example demonstrates using the [`Format`] [`CellOption`] factory to alter +//! the cells of a [`Table`]. +//! +//! * Note how [`Format::content()`] gives access to the respective cell content for replacement. +//! And [`Format::positioned()`] additionally provides the index coordinates of that cell. +//! +//! * Note how the [std] [`format!`] macro is used to update the values of the affected cells. + +use tabled::{ + settings::{ + object::{Columns, Object, Rows}, + Format, Modify, Style, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Commit { + id: &'static str, + header: &'static str, + message: &'static str, +} + +fn main() { + let data = [ + Commit { + header: "bypass open-source transmitter", + message: "index neural panel", + id: "8ae4e8957caeaa467acbce963701e227af00a1c7", + }, + Commit { + header: "program online alarm", + message: "copy bluetooth card", + id: "48c76de71bd685486d97dc8f4f05aa6fcc0c3f86", + }, + Commit { + header: "CSV", + message: "reboot mobile capacitor", + id: "6ffc2a2796229fc7bf59471ad907f58b897005d0", + }, + ]; + + let table = Table::new(data) + .with(Style::psql()) + .with( + Modify::new(Rows::first()) + .with(Format::positioned(|_, (_, column)| column.to_string())), + ) + .with( + Modify::new(Columns::first().not(Rows::first())) + .with(Format::content(|s| format!("{s}..."))), + ) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/formatting_settings.rs b/vendor/tabled/examples/formatting_settings.rs new file mode 100644 index 000000000..19c8a4806 --- /dev/null +++ b/vendor/tabled/examples/formatting_settings.rs @@ -0,0 +1,47 @@ +//! This example demonstrates using the [`Alignment`], [`AlignmentStrategy`], and [`TrimStrategy`] [`CellOptions`] +//! to align the content of a [`Table`] in several nuanced ways. +//! +//! * Note how [`AlignmentStrategy`] and [`TrimStrategy`] provide useful tools for managing multiline cells and +//! cell values that are bloated with whitespace. + +use tabled::{ + settings::{ + formatting::{AlignmentStrategy, TrimStrategy}, + object::Segment, + Alignment, Modify, Style, + }, + Table, +}; + +fn main() { + let some_json = r#" +[ + "foo", + { + "bar": 1, + "baz": [ + 2, + 3 + ] + } +]"#; + + let mut table = Table::new([some_json]); + table + .with(Style::rounded()) + .with(Modify::new(Segment::all()).with(Alignment::center())); + + println!("A default Alignment settings\n{table}"); + + table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); + + println!("Per line Alignment strategy\n{table}"); + + table.with( + Modify::new(Segment::all()) + .with(AlignmentStrategy::PerCell) + .with(TrimStrategy::Both), + ); + + println!("A default Alignment; allowing vertical and horizontal trim\n{table}"); +} diff --git a/vendor/tabled/examples/grid_colors.rs b/vendor/tabled/examples/grid_colors.rs new file mode 100644 index 000000000..f48b39f49 --- /dev/null +++ b/vendor/tabled/examples/grid_colors.rs @@ -0,0 +1,46 @@ +//! This example demonstrates using [`Color`] as a [`CellOption`] modifier to stylize +//! the cells of a [`Table`]. +//! +//! * Note how the [`Color`] [setting](tabled::settings) is used to simplify creating +//! reusable themes for backgrounds. + +use tabled::{ + settings::{Color, Modify, Style}, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Bsd { + distribution: &'static str, + year_of_first_release: usize, + is_active: bool, +} + +impl Bsd { + fn new(distribution: &'static str, year_of_first_release: usize, is_active: bool) -> Self { + Self { + distribution, + year_of_first_release, + is_active, + } + } +} + +fn main() { + let data = vec![ + Bsd::new("BSD", 1978, false), + Bsd::new("SunOS", 1982, false), + Bsd::new("NetBSD", 1993, true), + Bsd::new("FreeBSD", 1993, true), + Bsd::new("OpenBSD", 1995, true), + ]; + + let mut table = Table::new(data); + table + .with(Style::psql()) + .with(Modify::new((0, 0)).with(Color::BG_BLUE)) + .with(Modify::new((1, 1)).with(Color::BG_GREEN)) + .with(Modify::new((2, 2)).with(Color::BG_RED)); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/height.rs b/vendor/tabled/examples/height.rs new file mode 100644 index 000000000..2da0f36d9 --- /dev/null +++ b/vendor/tabled/examples/height.rs @@ -0,0 +1,49 @@ +//! This example demonstrates using the [`Height`] [`TableOption`] for adjusting +//! the height of a [`Table`]. +//! +//! * [`Height`] supports three key features: +//! * [`CellHeightIncrease`] spreads new whitespace between the [`Table`] +//! rows up to the specified line count. +//! * [`CellHeightLimit`] removes lines from the [`Table`] rows fairly, until +//! it has no choice but to remove single-line-rows entirely, bottom up. +//! * [`HeightList`] accepts an array of height specifications that are applied +//! to the rows with the same index. This is helpful for granularly specifying individual +//! row heights irrespective of [`Padding`] or [`Margin`]. + +use tabled::{ + settings::{peaker::PriorityMax, Height, Style}, + Table, +}; + +fn main() { + let data = vec![("Multi\nline\nstring", 123), ("Single line", 234)]; + + let mut table = Table::builder(data).build(); + table.with(Style::markdown()); + + println!("Table\n"); + println!("{table}"); + println!(); + + let table_ = table.clone().with(Height::increase(10)).to_string(); + + println!("Table increase height to 10\n"); + println!("{table_}"); + println!(); + + let table_ = table + .clone() + .with(Height::limit(4).priority::<PriorityMax>()) + .to_string(); + + println!("Table decrease height to 4\n"); + println!("{table_}"); + + let table_ = table + .clone() + .with(Height::limit(0).priority::<PriorityMax>()) + .to_string(); + + println!("Table decrease height to 0\n"); + println!("{table_}"); +} diff --git a/vendor/tabled/examples/highlight.rs b/vendor/tabled/examples/highlight.rs new file mode 100644 index 000000000..0b6abb8ad --- /dev/null +++ b/vendor/tabled/examples/highlight.rs @@ -0,0 +1,28 @@ +//! This example demonstrates using the [`Highlight`] [`TableOption`] to +//! decorate sections of a [`Table`] with a unique [`Border`]. +//! +//! * Note how [`Highlight`] arguments can be chained together to +//! create cross-sections and non-symmetrical shapes. + +use tabled::{ + settings::{ + object::{Columns, Object, Rows}, + style::{Border, Style}, + Highlight, + }, + Table, +}; + +fn main() { + let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; + + let table = Table::new(data) + .with(Style::modern()) + .with(Highlight::new( + Rows::first().and(Columns::single(1)), + Border::filled('*'), + )) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/highlight_color.rs b/vendor/tabled/examples/highlight_color.rs new file mode 100644 index 000000000..4646f3819 --- /dev/null +++ b/vendor/tabled/examples/highlight_color.rs @@ -0,0 +1,27 @@ +//! This example demonstrates using [`Highlight`] in combination with [`BorderColor`] to +//! frame sections of a [`Table`] with a unique background [`Color`]. +//! +//! * Note how [`Highlight::colored()`] is used to accept the necessary input instead of [`Highlight::new()`]. + +use tabled::{ + settings::{ + object::{Columns, Object, Rows}, + style::BorderColor, + Color, Highlight, Style, + }, + Table, +}; + +fn main() { + let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; + + let table = Table::new(data) + .with(Style::modern()) + .with(Highlight::colored( + Rows::first().and(Columns::single(1)), + BorderColor::filled(Color::BG_BRIGHT_BLACK), + )) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/hyperlink.rs b/vendor/tabled/examples/hyperlink.rs new file mode 100644 index 000000000..9fda7a040 --- /dev/null +++ b/vendor/tabled/examples/hyperlink.rs @@ -0,0 +1,83 @@ +//! This example demonstrates how hyperlinks can be embedded into a [`Table`] display. +//! +//! While not a [`tabled`] specific implementation, it is helpful to know that +//! most users expect certain elements of interactivity based on the purpose of your display. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * ⚠️ Terminal interfaces may differ in how they parse links or make them interactive. +//! [`tabled`] doesn't have the final say on whether a link is clickable or not. + +use tabled::{ + settings::{object::Segment, Alignment, Modify, Style, Width}, + Table, Tabled, +}; + +fn main() { + let multicolored_debian = "\x1b[30mDebian\x1b[0m\ + \x1b[31m Debian\x1b[0m\ + \x1b[32m Debian\x1b[0m\ + \x1b[33m Debian\x1b[0m\ + \x1b[34m Debian\x1b[0m\ + \x1b[35m Debian\x1b[0m\ + \x1b[36m Debian\x1b[0m\ + \x1b[37m Debian\x1b[0m\ + \x1b[40m Debian\x1b[0m\ + \x1b[41m Debian\x1b[0m\ + \x1b[42m Debian\x1b[0m\ + \x1b[43m Debian\x1b[0m\ + \x1b[44m Debian\x1b[0m"; + + let debian_repeat = + "DebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebian" + .to_string(); + + let debian_colored_link = format_osc8_hyperlink("https://www.debian.org/", multicolored_debian); + let debian_link = format_osc8_hyperlink("https://www.debian.org/", "Debian"); + let wiki_link = format_osc8_hyperlink("https://www.wikipedia.org/", "Debian"); + + let data = [ + Distribution::new("Debian".into(), false), + Distribution::new(debian_link.clone(), true), + Distribution::new(format!("{debian_link} a link followed by text"), true), + Distribution::new( + format!("{debian_link} links with intervening text {wiki_link}"), + true, + ), + Distribution::new(format!("a link surrounded {debian_link} by text"), true), + Distribution::new(debian_colored_link, true), + Distribution::new(debian_repeat, false), + ]; + + let mut table = Table::new(&data); + table + .with(Style::ascii_rounded()) + .with(Alignment::left()) + .with(Modify::new(Segment::all()).with(Width::wrap(16).keep_words())); + + println!("{table}"); + + let mut table = Table::new(&data); + table + .with(Style::ascii_rounded()) + .with(Alignment::left()) + .with(Modify::new(Segment::all()).with(Width::wrap(16))); + + println!("{table}"); +} + +#[derive(Tabled)] +struct Distribution { + name: String, + is_hyperlink: bool, +} + +impl Distribution { + fn new(name: String, is_hyperlink: bool) -> Self { + Self { name, is_hyperlink } + } +} + +fn format_osc8_hyperlink(url: &str, text: &str) -> String { + format!("\x1b]8;;{url}\x1b\\{text}\x1b]8;;\x1b\\",) +} diff --git a/vendor/tabled/examples/iter_table.rs b/vendor/tabled/examples/iter_table.rs new file mode 100644 index 000000000..0a77f7435 --- /dev/null +++ b/vendor/tabled/examples/iter_table.rs @@ -0,0 +1,31 @@ +//! This example demonstrates using [`IterTable`], an [allocation](https://doc.rust-lang.org/nomicon/vec/vec-alloc.html) +//! free [`Table`] alternative that translates an iterator into a display. +//! +//! * Note how [`IterTable`] supports the familiar `.with()` syntax for applying display +//! modifications. +//! +//! * [`IterTable`] supports manual configuration of: +//! * Record sniffing (default 1000 rows) +//! * Row cutoff +//! * Row height +//! * Column cutoff +//! * Column width + +use std::io::BufRead; + +use tabled::{settings::Style, tables::IterTable}; + +fn main() { + let path = file!(); + let file = std::fs::File::open(path).unwrap(); + let reader = std::io::BufReader::new(file); + let iterator = reader.lines().enumerate().map(|(i, line)| match line { + Ok(line) => [i.to_string(), String::from("ok"), line], + Err(err) => [i.to_string(), String::from("error"), err.to_string()], + }); + + let table = IterTable::new(iterator).with(Style::ascii_rounded()); + + table.build(std::io::stdout()).unwrap(); + println!() +} diff --git a/vendor/tabled/examples/margin.rs b/vendor/tabled/examples/margin.rs new file mode 100644 index 000000000..97498fb40 --- /dev/null +++ b/vendor/tabled/examples/margin.rs @@ -0,0 +1,21 @@ +//! This example demonstrates using the [`Margin`] [`TableOption`] to buffer space +//! around a [`Table`] display. +//! +//! * Note how the [`Margin::fill()`] function allows for overriding the default whitespace +//! with any [`char`]. + +use tabled::{ + settings::{Margin, Style}, + Table, +}; + +fn main() { + let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; + + let table = Table::new(data) + .with(Style::re_structured_text()) + .with(Margin::new(4, 3, 2, 1).fill('<', '>', 'v', '^')) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/matrix.rs b/vendor/tabled/examples/matrix.rs new file mode 100644 index 000000000..59e22ae9c --- /dev/null +++ b/vendor/tabled/examples/matrix.rs @@ -0,0 +1,27 @@ +//! This example demonstrates how [`tabled`] is an excellent tool for creating +//! dataset visualizations. +//! +//! * 🚀 When native display solutions, such as the [`Debug`] trait and [pretty printing](https://doc.rust-lang.org/std/fmt/#sign0) +//! options, aren't enough, [`tabled`] is a great choice for improving the quality of your displays. + +use tabled::{settings::Style, Table}; + +fn matrix<const N: usize>() -> [[usize; N]; N] { + let mut matrix = [[0; N]; N]; + + #[allow(clippy::needless_range_loop)] + for i in 0..N { + for j in 0..N { + matrix[i][j] = (i + 1) * (j + 1); + } + } + + matrix +} + +fn main() { + let data = matrix::<10>(); + let table = Table::new(data).with(Style::modern()).to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/merge_duplicates.rs b/vendor/tabled/examples/merge_duplicates.rs new file mode 100644 index 000000000..250a95a56 --- /dev/null +++ b/vendor/tabled/examples/merge_duplicates.rs @@ -0,0 +1,58 @@ +//! This example demonstrates using the [`Merge`] [`TableOption`] to clarify +//! redundancies in a [`Table`] display. +//! +//! * Note how repetative entries must be consecutive, in their specified direction, +//! to be merged together. +//! +//! * Note how [`BorderSpanCorrection`] is used to resolve display issues incurred +//! from [`Span`] decisions made through duplicate detection. +//! +//! * Merge supports both [`Merge::vertical()`] and [`Merge::horizontal()`]. + +use tabled::{ + settings::{ + style::{BorderSpanCorrection, Style}, + Merge, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct DatabaseTable { + #[tabled(rename = "db")] + db_name: &'static str, + #[tabled(rename = "table")] + table_name: &'static str, + total: usize, +} + +impl DatabaseTable { + fn new(db_name: &'static str, table_name: &'static str, total: usize) -> Self { + Self { + db_name, + table_name, + total, + } + } +} + +fn main() { + let data = [ + DatabaseTable::new("database_1", "table_1", 10712), + DatabaseTable::new("database_1", "table_2", 57), + DatabaseTable::new("database_1", "table_3", 57), + DatabaseTable::new("database_2", "table_1", 72), + DatabaseTable::new("database_2", "table_2", 75), + DatabaseTable::new("database_3", "table_1", 20), + DatabaseTable::new("database_3", "table_2", 21339), + DatabaseTable::new("database_3", "table_3", 141723), + ]; + + let table = Table::new(data) + .with(Merge::vertical()) + .with(Style::modern()) + .with(BorderSpanCorrection) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/merge_duplicates_2.rs b/vendor/tabled/examples/merge_duplicates_2.rs new file mode 100644 index 000000000..063664d3c --- /dev/null +++ b/vendor/tabled/examples/merge_duplicates_2.rs @@ -0,0 +1,78 @@ +//! This example demonstrates using the [`Merge`] [`TableOption`] to clarify +//! redundancies in a [`Table`] display. +//! +//! * Note how a custom theme is applied to give the [`Merged`](Merge) cells +//! a unique look. +//! +//! * Merge supports both [`Merge::vertical()`] and [`Merge::horizontal()`]. + +use tabled::{ + settings::{ + object::{Cell, Columns, Object, Rows}, + style::{Border, BorderSpanCorrection, Style}, + Merge, Modify, + }, + Table, Tabled, +}; + +fn main() { + let data = [ + DatabaseTable::new("database_1", "database_1", "table_1", 10712), + DatabaseTable::new("database_1", "database_1", "table_2", 57), + DatabaseTable::new("database_1", "database_1", "table_3", 57), + DatabaseTable::new("database_2", "", "table_1", 72), + DatabaseTable::new("database_2", "", "table_2", 75), + DatabaseTable::new("database_3", "database_3", "table_1", 20), + DatabaseTable::new("database_3", "", "table_2", 21339), + DatabaseTable::new("database_3", "", "table_3", 141723), + ]; + + let mut table = Table::builder(data).index().transpose().build(); + config_theme(&mut table); + table.with(Merge::horizontal()).with(BorderSpanCorrection); + + println!("{table}"); +} + +#[derive(Tabled)] +struct DatabaseTable { + #[tabled(rename = "db")] + db_name: &'static str, + origin_db: &'static str, + #[tabled(rename = "table")] + table_name: &'static str, + total: usize, +} + +impl DatabaseTable { + fn new( + db_name: &'static str, + origin_db: &'static str, + table_name: &'static str, + total: usize, + ) -> Self { + Self { + db_name, + origin_db, + table_name, + total, + } + } +} + +fn config_theme(table: &mut Table) { + table + .with(Style::rounded().remove_vertical()) + .with(Modify::new(Columns::first()).with(Border::default().right('│'))) + .with( + Modify::new(Cell::new(0, 0)).with( + Border::default() + .corner_top_right('┬') + .corner_bottom_right('┼'), + ), + ) + .with( + Modify::new(Columns::first().intersect(Rows::last())) + .with(Border::default().corner_bottom_right('┴')), + ); +} diff --git a/vendor/tabled/examples/nested_table.rs b/vendor/tabled/examples/nested_table.rs new file mode 100644 index 000000000..8884c81c1 --- /dev/null +++ b/vendor/tabled/examples/nested_table.rs @@ -0,0 +1,117 @@ +//! This example demonstrates how [`Tables`](Table) can be comprised of other tables. +//! +//! * This first nested [`Table`] example showcases the [`Builder`] approach. +//! +//! * Note how a great deal of manual customizations have been applied to create a +//! highly unique display. +//! +//! * 🎉 Inspired by https://github.com/p-ranav/tabulate#nested-tables/ + +use std::iter::FromIterator; + +use tabled::{ + builder::Builder, + settings::{ + object::{Rows, Segment}, + style::{HorizontalLine, Style}, + Alignment, Modify, Padding, Width, + }, + Table, +}; + +fn main() { + let animal = create_class( + "Animal", + &[("age", "Int", ""), ("gender", "String", "")], + &["isMammal", "mate"], + ); + + let duck = create_class( + "Duck", + &[("beakColor", "String", "yellow")], + &["swim", "quack"], + ); + + let mut table = Builder::from_iter([ + [animal.to_string()], + [String::from("▲")], + [String::from("|")], + [String::from("|")], + [duck.to_string()], + ]) + .build(); + table.with(Style::ascii().remove_horizontal()).with( + Modify::new(Segment::all()) + .with(Padding::new(5, 5, 0, 0)) + .with(Alignment::center()), + ); + + println!("{table}"); +} + +fn create_class(name: &str, fields: &[(&str, &str, &str)], methods: &[&str]) -> Table { + let fields = fields + .iter() + .map(|(field, t, d)| [format_field(field, t, d)]); + let mut table_fields = Builder::from_iter(fields).build(); + table_fields.with(Style::ascii().remove_horizontal().remove_vertical()); + + let methods = methods.iter().map(|method| [format_method(method)]); + let mut table_methods = Builder::from_iter(methods).build(); + table_methods.with(Style::ascii().remove_horizontal().remove_vertical()); + + let (table_fields, table_methods) = make_equal_width(table_fields, table_methods); + + let mut table = Builder::from_iter([ + [name.to_string()], + [table_fields.to_string()], + [table_methods.to_string()], + ]) + .build(); + + table + .with( + Style::ascii() + .horizontals([HorizontalLine::new(1, Style::ascii().get_horizontal())]) + .remove_horizontal() + .remove_vertical(), + ) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Rows::first()).with(Alignment::center())); + + table +} + +fn format_field(field: &&str, field_type: &&str, default_value: &&str) -> String { + if default_value.is_empty() { + format!("+{field}: {field_type}") + } else { + format!("+{field}: {field_type} = {default_value:?}") + } +} + +fn format_method(method: &str) -> String { + format!("+{method}()") +} + +fn make_equal_width(mut table1: Table, mut table2: Table) -> (Table, Table) { + // We want to make a fields table and methods table to have the same width. + // To not set it to constant, we check a width of each of them and correct the other. + // + // it's safe to do .len() because we use ascii theme and no colors. + + let table1_width = table1.to_string().lines().next().unwrap().len(); + let table2_width = table2.to_string().lines().next().unwrap().len(); + + match table1_width.cmp(&table2_width) { + std::cmp::Ordering::Less => { + table1.with(Width::increase(table2_width)); + } + std::cmp::Ordering::Greater => { + table2.with(Width::increase(table1_width)); + } + std::cmp::Ordering::Equal => (), + } + + (table1, table2) +} diff --git a/vendor/tabled/examples/nested_table_2.rs b/vendor/tabled/examples/nested_table_2.rs new file mode 100644 index 000000000..40b0cd0a9 --- /dev/null +++ b/vendor/tabled/examples/nested_table_2.rs @@ -0,0 +1,90 @@ +//! This example demonstrates a minimalist implementation of [`Tabling`](Table) records +//! with struct fields. +//! +//! * This second nested [`Table`] example showcases the [`derive`] approach. +//! +//! * Note how the [`display_with`] attribute macro applies the custom `display_distribution` +//! filter function, which, in this case, applies styles to the final display. + +use tabled::{settings::Style, Table, Tabled}; + +#[derive(Tabled)] +struct Vendor { + name: &'static str, + #[tabled(display_with = "display_distribution")] + main_os: Distribution, + #[tabled(display_with = "display_distribution")] + switch_os: Distribution, +} + +impl Vendor { + fn new(name: &'static str, main_os: Distribution, switch_os: Distribution) -> Self { + Self { + name, + main_os, + switch_os, + } + } +} + +fn display_distribution(d: &Distribution) -> String { + Table::new([d]).with(Style::extended()).to_string() +} + +#[derive(Tabled)] +struct Distribution { + name: &'static str, + #[tabled(display_with = "Self::display_based_on")] + based_on: Option<&'static str>, + is_active: bool, + is_cool: bool, +} + +impl Distribution { + fn display_based_on(o: &Option<&'static str>) -> String { + match o { + &Some(s) => s.into(), + None => "Independent".into(), + } + } +} + +impl Distribution { + fn new( + name: &'static str, + based_on: Option<&'static str>, + is_active: bool, + is_cool: bool, + ) -> Self { + Self { + name, + based_on, + is_active, + is_cool, + } + } +} + +fn main() { + let data = [ + Vendor::new( + "Azure", + Distribution::new("Windows", None, true, true), + Distribution::new("Manjaro", Some("Arch"), true, true), + ), + Vendor::new( + "AWS", + Distribution::new("Debian", None, true, true), + Distribution::new("Arch", None, true, true), + ), + Vendor::new( + "GCP", + Distribution::new("Debian", None, true, true), + Distribution::new("Arch", None, true, true), + ), + ]; + + let table = Table::new(data).with(Style::modern()).to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/nested_table_3.rs b/vendor/tabled/examples/nested_table_3.rs new file mode 100644 index 000000000..8e79b27a5 --- /dev/null +++ b/vendor/tabled/examples/nested_table_3.rs @@ -0,0 +1,56 @@ +//! This example demonstrates creating a nested [`Table`] by instantiating a new +//! [`Table`] from a collection of other [`Tables`](Table). +//! +//! * This third nested [`Table`] example showcases the [`Table::new()`] approach. + +use tabled::{ + settings::{ + object::{Cell, Segment}, + Alignment, Border, Extract, Highlight, Modify, Panel, Style, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Contribution { + author: &'static str, + profile: &'static str, +} + +impl Contribution { + fn new(author: &'static str, profile: &'static str) -> Self { + Self { author, profile } + } +} + +fn main() { + let committers = [ + Contribution::new("kozmod", "https:/github.com/kozmod"), + Contribution::new("IsaacCloos", "https:/github.com/IsaacCloos"), + ]; + + let issuers = [Contribution::new( + "aharpervc", + "https:/github.com/aharpervc", + )]; + + let committers_table = Table::new(committers) + .with(Panel::header("Contributors")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .to_string(); + + let issues_table = Table::new(issuers) + .with(Panel::header("Issuers")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .to_string(); + + let mut a_welcome_table = + Table::new([String::from("Thank You"), committers_table, issues_table]); + a_welcome_table + .with(Extract::rows(1..)) + .with(Style::ascii().remove_horizontal()) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Highlight::new(Cell::new(0, 0), Border::filled('*'))); + + println!("{a_welcome_table}"); +} diff --git a/vendor/tabled/examples/panel.rs b/vendor/tabled/examples/panel.rs new file mode 100644 index 000000000..343abcd67 --- /dev/null +++ b/vendor/tabled/examples/panel.rs @@ -0,0 +1,63 @@ +//! This example demonstrates using the [`Panel`] [`TableOption`] to inject +//! table-length columns and rows into a [`Table`]. +//! +//! * [`Panel`] supports four injection options: +//! * Horizontal | manual index selection +//! * Vertical | manual index selection +//! * Header | before first row +//! * Footer | after last row + +use tabled::{ + settings::{ + object::Segment, + style::{BorderSpanCorrection, Style}, + Alignment, Modify, Panel, Width, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct Release { + version: &'static str, + published_date: &'static str, + is_active: bool, + major_feature: &'static str, +} + +impl Release { + const fn new( + version: &'static str, + published_date: &'static str, + is_active: bool, + major_feature: &'static str, + ) -> Self { + Self { + version, + published_date, + is_active, + major_feature, + } + } +} + +const DATA: [Release; 3] = [ + Release::new("0.2.1", "2021-06-23", true, "#[header(inline)] attribute"), + Release::new("0.2.0", "2021-06-19", false, "API changes"), + Release::new("0.1.4", "2021-06-07", false, "display_with attribute"), +]; + +fn main() { + let mut table = Table::new(DATA); + table + .with(Panel::header("Tabled Releases")) + .with(Panel::footer(format!("N - {}", DATA.len()))) + .with(Panel::vertical(0, "Some text goes here")) + .with(Panel::vertical(5, "Some text goes here")) + .with(Modify::new((0, 0)).with(Width::wrap(1))) + .with(Modify::new((0, 5)).with(Width::wrap(1))) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::modern()) + .with(BorderSpanCorrection); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/pool_table.rs b/vendor/tabled/examples/pool_table.rs new file mode 100644 index 000000000..593631e06 --- /dev/null +++ b/vendor/tabled/examples/pool_table.rs @@ -0,0 +1,33 @@ +//! This example demonstrates a [`PoolTable`] usage. + +use tabled::{ + settings::{Alignment, Style}, + tables::{PoolTable, TableValue}, +}; + +fn main() { + let data = vec![ + vec!["Hello World", "Hello World", "Hello World"], + vec!["Hello", "", "Hello"], + vec!["W", "o", "r", "l", "d"], + ]; + + let data = TableValue::Column( + data.into_iter() + .map(|row| { + TableValue::Row( + row.into_iter() + .map(|text| TableValue::Cell(text.to_owned())) + .collect(), + ) + }) + .collect(), + ); + + let table = PoolTable::from(data) + .with(Style::modern()) + .with(Alignment::center()) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/pool_table2.rs b/vendor/tabled/examples/pool_table2.rs new file mode 100644 index 000000000..02b457250 --- /dev/null +++ b/vendor/tabled/examples/pool_table2.rs @@ -0,0 +1,26 @@ +//! This example demonstrates a [`PoolTable`] usage. + +use tabled::{ + settings::{Alignment, Style}, + tables::PoolTable, +}; + +fn main() { + let characters = [ + "Naruto Uzumaki", + "Kakashi Hatake", + "Minato Namikaze", + "Jiraiya", + "Orochimaru", + "Itachi Uchiha", + ]; + + let data = characters.chunks(2); + + let table = PoolTable::new(data) + .with(Style::dots()) + .with(Alignment::center()) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/rotate.rs b/vendor/tabled/examples/rotate.rs new file mode 100644 index 000000000..8db997527 --- /dev/null +++ b/vendor/tabled/examples/rotate.rs @@ -0,0 +1,38 @@ +//! This example demonstrates using the [`Rotate`] [`TableOption`] to rotate the cells +//! of a [`Table`]. +//! +//! * [`Rotate`] supports four motions: +//! * `Left` | 90 degree shift +//! * `Right` | 90 degree shift +//! * `Top` & `Bottom` | Reverse row order + +use tabled::{settings::Rotate, Table, Tabled}; + +#[derive(Tabled)] +struct Linux { + id: u8, + distribution: &'static str, + link: &'static str, +} + +impl Linux { + fn new(id: u8, distribution: &'static str, link: &'static str) -> Self { + Self { + id, + distribution, + link, + } + } +} + +fn main() { + let data = vec![ + Linux::new(0, "Fedora", "https://getfedora.org/"), + Linux::new(2, "OpenSUSE", "https://www.opensuse.org/"), + Linux::new(3, "Endeavouros", "https://endeavouros.com/"), + ]; + + let table = Table::new(data).with(Rotate::Left).to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/settings_list.rs b/vendor/tabled/examples/settings_list.rs new file mode 100644 index 000000000..b0b0aca4c --- /dev/null +++ b/vendor/tabled/examples/settings_list.rs @@ -0,0 +1,57 @@ +//! This example demonstrates using the [`Settings`] [`TableOption`] to array +//! [`Table`] configurations in a separate step from instantiation. +//! +//! * Note how this methodoly can lead to huge performance gains +//! with compile-time constants. + +use tabled::{ + settings::{ + object::{FirstRow, Rows}, + style::On, + Alignment, Modify, ModifyList, Padding, Settings, Style, + }, + Table, Tabled, +}; + +#[derive(Tabled)] +struct CodeEditor { + name: &'static str, + first_release: &'static str, + developer: &'static str, +} + +impl CodeEditor { + fn new(name: &'static str, first_release: &'static str, developer: &'static str) -> Self { + Self { + name, + first_release, + developer, + } + } +} + +// unfortunately we can't leave it as a blank type, so we need to provide it. +type TableTheme = Settings< + Settings<Settings<Settings, Style<On, On, On, On, On, On>>, Padding>, + ModifyList<FirstRow, Alignment>, +>; + +const THEME: TableTheme = Settings::empty() + .with(Style::ascii()) + .with(Padding::new(1, 3, 0, 0)) + .with(Modify::list(Rows::first(), Alignment::center())); + +fn main() { + let data = [ + CodeEditor::new("Sublime Text 3", "2008", "Sublime HQ"), + CodeEditor::new("Visual Studio Code", "2015", "Microsoft"), + CodeEditor::new("Notepad++", "2003", "Don Ho"), + CodeEditor::new("GNU Emacs", "1984", "Richard Stallman"), + CodeEditor::new("Neovim", "2015", "Vim community"), + ]; + + let mut table = Table::new(data); + table.with(THEME); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/shadow.rs b/vendor/tabled/examples/shadow.rs new file mode 100644 index 000000000..baa856ed9 --- /dev/null +++ b/vendor/tabled/examples/shadow.rs @@ -0,0 +1,160 @@ +//! This example can be run with the following command: +//! +//! `echo -e -n 'Some text\nIn the box' | cargo run --example shadow` +//! +//! This example demonstrates using the [`Shadow`] [`TableOption`] to create +//! a striking frame around a [`Table`] display. +//! +//! * [`Shadow`] supports several configurations: +//! * Thickness +//! * Offset +//! * Direction +//! * Color +//! * Fill character +//! +//! * 🎉 Inspired by <https://en.wikipedia.org/wiki/Box-drawing_character> + +use std::{io::Read, iter::FromIterator}; + +use tabled::{ + builder::Builder, + grid::util::string, + row, + settings::{ + object::Cell, + style::{BorderChar, Offset, RawStyle, Style}, + Height, Modify, Padding, Shadow, Width, + }, + Table, +}; + +fn main() { + let message = read_message(); + print_table(message); +} + +fn print_table(message: String) { + let main_table = create_main_table(&message); + let main_table_width = main_table.total_width(); + let small_table_row = create_small_table_list(main_table_width); + println!("{small_table_row}"); + println!("{main_table}"); +} + +fn read_message() -> String { + let mut buf = String::new(); + std::io::stdin().read_to_string(&mut buf).unwrap(); + + buf +} + +fn create_small_table_list(width_available: usize) -> String { + let mut tables = [ + create_small_table(Style::modern().into()), + create_small_table(Style::extended().into()), + create_small_table( + Style::modern() + .left('║') + .right('║') + .intersection_left('╟') + .intersection_right('╢') + .corner_top_right('╖') + .corner_top_left('╓') + .corner_bottom_right('╜') + .corner_bottom_left('╙') + .into(), + ), + create_small_table( + Style::modern() + .top('═') + .bottom('═') + .corner_top_right('╕') + .corner_top_left('╒') + .corner_bottom_right('╛') + .corner_bottom_left('╘') + .horizontal('═') + .intersection_left('╞') + .intersection_right('╡') + .intersection_top('╤') + .intersection_bottom('╧') + .intersection('╪') + .into(), + ), + ]; + const TOTAL_TABLE_WIDTH: usize = 19; + + if width_available > TOTAL_TABLE_WIDTH { + let mut rest = width_available - TOTAL_TABLE_WIDTH; + while rest > 0 { + for table in &mut tables { + let current_width = table.total_width(); + table.with(Width::increase(current_width + 1)); + rest -= 1; + + if rest == 0 { + break; + } + } + } + } + + let small_table_row = row![tables[0], tables[1], tables[2], tables[3]] + .with(Style::blank()) + .with(Padding::zero()) + .to_string(); + small_table_row +} + +fn create_small_table(style: RawStyle) -> Table { + let mut table = Builder::from_iter(vec![vec![" ", ""], vec![" ", ""]]).build(); + table + .with(style) + .with(Padding::zero()) + .with(Height::list([1, 0])); + + table +} + +fn create_main_table(message: &str) -> Table { + let (count_lines, message_width) = string::string_dimension(message); + let count_additional_separators = if count_lines > 2 { count_lines - 2 } else { 0 }; + + let left_table = format!( + " ╔═══╗ \n ╚═╦═╝ \n{}═╤══╩══╤\n ├──┬──┤\n └──┴──┘", + (0..count_additional_separators) + .map(|_| " ║ \n") + .collect::<String>() + ); + + let message = if count_lines < 2 { + let mut i = count_lines; + let mut buf = message.to_string(); + while i < 2 { + buf.push('\n'); + i += 1; + } + + buf + } else { + message.to_owned() + }; + let count_lines = count_lines.max(2); + + let message = format!("{}\n{}", message, "═".repeat(message_width)); + + let mut table = row![left_table, message]; + table + .with(Padding::zero()) + .with(Style::modern().remove_vertical()) + .with( + Modify::new(Cell::new(0, 0)) + .with(BorderChar::vertical('╞', Offset::Begin(count_lines))), + ) + .with( + Modify::new(Cell::new(0, 2)) + .with(BorderChar::vertical('╡', Offset::Begin(count_lines))), + ) + .with(Shadow::new(2)); + + table +} diff --git a/vendor/tabled/examples/span.rs b/vendor/tabled/examples/span.rs new file mode 100644 index 000000000..e6a75576a --- /dev/null +++ b/vendor/tabled/examples/span.rs @@ -0,0 +1,40 @@ +//! This example demonstrates using the [`Span`] [`CellOption`] to +//! extend [Cells](Cell) over a specified number of columns/rows. +//! +//! * Note how [`Span`] is available for [`Cell`] modifications +//! after the [`Modify`] [`TableOption`] is applied. +//! +//! * ⚠️ `with()` is a reused pattern within [`tabled`] for both [`Table`] +//! and [`Cell`] modifications. It can be easy for beginners to mistakenly +//! try to pass [`settings`] intended for one to the other. + +use tabled::{ + settings::{ + object::Cell, + style::{BorderSpanCorrection, Style}, + Alignment, Modify, Span, + }, + Table, +}; + +fn main() { + let data = [["just 1 column"; 5]; 5]; + + let h_span = |r, c, span| Modify::new(Cell::new(r, c)).with(Span::row(span)); + let v_span = |r, c, span| Modify::new(Cell::new(r, c)).with(Span::column(span)); + + let table = Table::new(data) + .with(h_span(0, 0, 5).with("span all 5 columns")) + .with(h_span(1, 0, 4).with("span 4 columns")) + .with(h_span(2, 0, 2).with("span 2 columns")) + .with(v_span(2, 4, 4).with("just 1 column\nspan\n4\ncolumns")) + .with(v_span(3, 1, 2).with("span 2 columns\nspan\n2\ncolumns")) + .with(v_span(2, 3, 3).with("just 1 column\nspan\n3\ncolumns")) + .with(h_span(3, 1, 2)) + .with(Style::modern()) + .with(BorderSpanCorrection) + .with(Alignment::center_vertical()) + .to_string(); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/split.rs b/vendor/tabled/examples/split.rs new file mode 100644 index 000000000..d8e6c679a --- /dev/null +++ b/vendor/tabled/examples/split.rs @@ -0,0 +1,43 @@ +//! This example demonstrates using the [`Split`] [`TableOption`] to +//! transform a [`Table`] display in multiple ways. +//! +//! * Several configurations are available to customize a [`Split`] instruction: +//! * [`Index`](usize) +//! * [`Behavior`] +//! * [`Direction`] +//! * [`Display`] + +use std::iter::FromIterator; +use tabled::{ + col, row, + settings::{split::Split, style::Style, Padding}, + Table, +}; + +fn main() { + let mut table = Table::from_iter(['a'..='z']); + table.with(Style::modern()); + + let table_1 = table.clone().with(Split::column(12)).clone(); + let table_2 = table_1.clone().with(Split::column(2).zip()).to_string(); + let table_3 = table_1.clone().with(Split::column(2).concat()).to_string(); + let table_4 = table_1.clone().with(Split::row(2).zip()).to_string(); + let table_5 = table_1.clone().with(Split::row(2).concat()).to_string(); + + let mut table = col![ + table, + row![ + table_1, + table_2, + table_3, + col![table_4, table_5] + .with(Style::blank()) + .with(Padding::zero()) + ] + .with(Style::blank()) + .with(Padding::zero()), + ]; + table.with(Style::blank()); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/table.rs b/vendor/tabled/examples/table.rs new file mode 100644 index 000000000..d6d5c392c --- /dev/null +++ b/vendor/tabled/examples/table.rs @@ -0,0 +1,56 @@ +//! This example demonstrates the fundemental qualities of the [crate](https://crates.io/crates/tabled) [`tabled`]. +//! +//! * [`tabled`] is powered by convenient [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html#procedural-macros) +//! like [`Tabled`]; a deriveable trait that allows your custom +//! structs and enums to be represented with [`tabled`]'s powerful suite of features. +//! +//! * [`Table`] is the root object to which all of [`tabled`]'s implementation +//! tools guide you, and from which all of its customization options derive value. +//! The READMEs, examples, and docs found in this project will show you many dozens +//! of ways you can build tables, and show you how to use the available tools +//! to build the data representations that best fit your needs. +//! +//! * [`Table::with()`] plays a central role in giving the user control over their +//! displays. A majority of [`Table`] customizations are implemented through this highly +//! dynamic function. A few [`TableOptions`](TableOption) include: +//! * [`Style`] +//! * [`Modify`] +//! * [`Alignment`] +//! * [`Padding`] + +use tabled::{ + settings::{object::Rows, Alignment, Modify, Style}, + Table, Tabled, +}; + +#[derive(Debug, Tabled)] +struct Distribution { + name: String, + based_on: String, + is_active: bool, +} + +impl Distribution { + fn new(name: &str, base: &str, is_active: bool) -> Self { + Self { + based_on: base.to_owned(), + name: name.to_owned(), + is_active, + } + } +} + +fn main() { + let data = [ + Distribution::new("Debian", "", true), + Distribution::new("Arch", "", true), + Distribution::new("Manjaro", "Arch", true), + ]; + + let mut table = Table::new(data); + table + .with(Style::markdown()) + .with(Modify::new(Rows::first()).with(Alignment::center())); + + println!("{table}"); +} diff --git a/vendor/tabled/examples/table_width.rs b/vendor/tabled/examples/table_width.rs new file mode 100644 index 000000000..372adb489 --- /dev/null +++ b/vendor/tabled/examples/table_width.rs @@ -0,0 +1,40 @@ +//! This example demonstrates using the [`Width`] [`TableOption`] to expand and +//! contract a [`Table`] display. +//! +//! * Note how table-wide size adjustments are applied proportionally to all columns. +//! +//! * Note how [fluent](https://en.wikipedia.org/wiki/Fluent_interface) functions +//! are available to make subtle customizations to [`Width`] primary features like +//! [`Width::truncate()`], [`Width::increase()`], and [`Width::wrap()`]. + +use tabled::{ + settings::{measurement::Percent, object::Segment, Alignment, Modify, Style, Width}, + Table, +}; + +fn main() { + let data = [ + ["Hello World!!!", "3.3.22.2"], + ["Guten Morgen", "1.1.1.1"], + ["Добры вечар", "127.0.0.1"], + ["Bonjour le monde", ""], + ["Ciao mondo", ""], + ]; + + let mut table = Table::builder(data).build(); + table.with(Style::markdown()).with(Alignment::left()); + + println!("Original table\n{table}\n"); + + table.with(Width::truncate(20).suffix("...")); + + println!("Truncated table\n{table}\n"); + + table.with(Modify::new(Segment::all()).with(Width::wrap(5))); + + println!("Wrapped table\n{table}\n"); + + table.with(Width::increase(Percent(200))); + + println!("Widen table\n{table}"); +} diff --git a/vendor/tabled/examples/table_width_2.rs b/vendor/tabled/examples/table_width_2.rs new file mode 100644 index 000000000..d2601fc87 --- /dev/null +++ b/vendor/tabled/examples/table_width_2.rs @@ -0,0 +1,23 @@ +//! This example demonstrates using [`Wrap::keep_words()`] to preserve +//! word shape while truncating a table to the specified size. Without +//! this setting enabled, a word could possibly be split into pieces, +//! greatly reducing the legibility of the display. + +use tabled::{ + settings::{object::Segment, Alignment, Modify, Style, Width}, + Table, +}; + +fn main() { + let readme_text = include_str!("../../CHANGELOG.md"); + let lines = readme_text.lines().filter(|s| !s.is_empty()).enumerate(); + + let mut table = Table::new(lines); + table.with(Style::ascii_rounded()).with( + Modify::new(Segment::all()) + .with(Width::wrap(30).keep_words()) + .with(Alignment::left()), + ); + + println!("{table}"); +} diff --git a/vendor/tabled/src/builder/index_builder.rs b/vendor/tabled/src/builder/index_builder.rs new file mode 100644 index 000000000..081f580df --- /dev/null +++ b/vendor/tabled/src/builder/index_builder.rs @@ -0,0 +1,318 @@ +use crate::Table; + +use super::Builder; + +/// [`IndexBuilder`] helps to add an index to the table. +/// +/// Index is a column on the left of the table. +/// +/// It also can be used to transpose the table. +/// +/// Creates a new [`IndexBuilder`] instance. +/// +/// It creates a default index a range from 0 to N. (N - count rows) +/// It also sets a default columns to the range 0 .. N (N - count columns). +///nfo<'a> +/// # Example +/// +/// ``` +/// use tabled::builder::Builder; +/// +/// let mut builder = Builder::default(); +/// builder.set_header(["i", "col-1", "col-2"]); +/// builder.push_record(["0", "value-1", "value-2"]); +/// +/// let table = builder.index().build().to_string(); +/// +/// assert_eq!( +/// table, +/// "+---+---+---------+---------+\n\ +/// | | i | col-1 | col-2 |\n\ +/// +---+---+---------+---------+\n\ +/// | 0 | 0 | value-1 | value-2 |\n\ +/// +---+---+---------+---------+" +/// ) +/// ``` +/// +/// # Example +/// +/// ``` +/// use tabled::builder::Builder; +/// +/// let table = Builder::default() +/// .index() +/// .build(); +/// ``` +#[derive(Debug, Clone)] +pub struct IndexBuilder { + /// Index is an index data. + /// It's always set. + index: Vec<String>, + /// Name of an index + name: Option<String>, + /// A flag which checks if we need to actually use index. + /// + /// It might happen when it's only necessary to [`Self::transpose`] table. + print_index: bool, + /// A flag which checks if table was transposed. + transposed: bool, + /// Data originated in [`Builder`]. + data: Vec<Vec<String>>, +} + +impl IndexBuilder { + /// No flag makes builder to not use an index. + /// + /// It may be useful when only [`Self::transpose`] need to be used. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "col-1", "col-2"]); + /// builder.push_record(["0", "value-1", "value-2"]); + /// builder.push_record(["2", "value-3", "value-4"]); + /// + /// let table = builder.index().hide().build().to_string(); + /// + /// assert_eq!( + /// table, + /// "+---+---------+---------+\n\ + /// | i | col-1 | col-2 |\n\ + /// +---+---------+---------+\n\ + /// | 0 | value-1 | value-2 |\n\ + /// +---+---------+---------+\n\ + /// | 2 | value-3 | value-4 |\n\ + /// +---+---------+---------+" + /// ) + /// ``` + pub fn hide(mut self) -> Self { + self.print_index = false; + self + } + + /// Set an index name. + /// + /// When [`None`] the name won't be used. + /// + /// # Example + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column1", "column2"]); + /// builder.push_record(["0", "value1", "value2"]); + /// + /// let table = builder.index() + /// .column(1) + /// .name(Some(String::from("index"))) + /// .build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+--------+---+---------+\n\ + /// | | i | column2 |\n\ + /// +--------+---+---------+\n\ + /// | index | | |\n\ + /// +--------+---+---------+\n\ + /// | value1 | 0 | value2 |\n\ + /// +--------+---+---------+" + /// ) + /// ``` + pub fn name(mut self, name: Option<String>) -> Self { + self.name = name; + self + } + + /// Sets a index to the chosen column. + /// + /// Also sets a name of the index to the column name. + /// + /// # Example + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column1", "column2"]); + /// builder.push_record(["0", "value1", "value2"]); + /// + /// let table = builder.index().column(1).build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+---------+---+---------+\n\ + /// | | i | column2 |\n\ + /// +---------+---+---------+\n\ + /// | column1 | | |\n\ + /// +---------+---+---------+\n\ + /// | value1 | 0 | value2 |\n\ + /// +---------+---+---------+" + /// ) + /// ``` + pub fn column(mut self, column: usize) -> Self { + if column >= matrix_count_columns(&self.data) { + return self; + } + + self.index = get_column(&mut self.data, column); + + let name = remove_or_default(&mut self.index, 0); + self.name = Some(name); + + self + } + + /// Transpose index and columns. + /// + /// # Example + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column-1", "column-2", "column-3"]); + /// builder.push_record(["0", "value-1", "value-2", "value-3"]); + /// builder.push_record(["1", "value-4", "value-5", "value-6"]); + /// builder.push_record(["2", "value-7", "value-8", "value-9"]); + /// + /// let table = builder.index().column(1).transpose().build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+----------+---------+---------+---------+\n\ + /// | column-1 | value-1 | value-4 | value-7 |\n\ + /// +----------+---------+---------+---------+\n\ + /// | i | 0 | 1 | 2 |\n\ + /// +----------+---------+---------+---------+\n\ + /// | column-2 | value-2 | value-5 | value-8 |\n\ + /// +----------+---------+---------+---------+\n\ + /// | column-3 | value-3 | value-6 | value-9 |\n\ + /// +----------+---------+---------+---------+" + /// ) + /// ``` + pub fn transpose(mut self) -> Self { + let columns = &mut self.data[0]; + std::mem::swap(&mut self.index, columns); + + let columns = self.data.remove(0); + + make_rows_columns(&mut self.data); + + self.data.insert(0, columns); + + self.transposed = !self.transposed; + + self + } + + /// Builds a table. + pub fn build(self) -> Table { + let builder: Builder = self.into(); + builder.build() + } +} + +impl From<Builder> for IndexBuilder { + fn from(builder: Builder) -> Self { + let has_header = builder.has_header(); + + let mut data: Vec<Vec<_>> = builder.into(); + + if !has_header { + let count_columns = matrix_count_columns(&data); + data.insert(0, build_range_index(count_columns)); + } + + // we exclude first row which contains a header + let data_len = data.len().saturating_sub(1); + let index = build_range_index(data_len); + + Self { + index, + name: None, + print_index: true, + transposed: false, + data, + } + } +} + +impl From<IndexBuilder> for Builder { + fn from(b: IndexBuilder) -> Self { + build_index(b) + } +} + +fn build_index(mut b: IndexBuilder) -> Builder { + if b.index.is_empty() { + return Builder::default(); + } + + // add index column + if b.print_index { + b.index.insert(0, String::default()); + + insert_column(&mut b.data, b.index, 0); + } + + if let Some(name) = b.name { + if b.transposed && b.print_index { + b.data[0][0] = name; + } else { + b.data.insert(1, vec![name]); + } + } + + Builder::from(b.data) +} + +fn build_range_index(n: usize) -> Vec<String> { + (0..n).map(|i| i.to_string()).collect() +} + +fn remove_or_default<T: Default>(v: &mut Vec<T>, i: usize) -> T { + if v.len() > i { + v.remove(i) + } else { + T::default() + } +} + +fn get_column<T: Default>(v: &mut [Vec<T>], col: usize) -> Vec<T> { + let mut column = Vec::with_capacity(v.len()); + for row in v.iter_mut() { + let value = remove_or_default(row, col); + column.push(value); + } + + column +} + +fn make_rows_columns<T: Default>(v: &mut Vec<Vec<T>>) { + let count_columns = matrix_count_columns(v); + + let mut columns = Vec::with_capacity(count_columns); + for _ in 0..count_columns { + let column = get_column(v, 0); + columns.push(column); + } + + v.clear(); + + for column in columns { + v.push(column); + } +} + +fn insert_column<T: Default>(v: &mut [Vec<T>], mut column: Vec<T>, col: usize) { + for row in v.iter_mut() { + let value = remove_or_default(&mut column, col); + row.insert(col, value); + } +} + +fn matrix_count_columns<T>(v: &[Vec<T>]) -> usize { + v.first().map_or(0, |row| row.len()) +} diff --git a/vendor/tabled/src/builder/mod.rs b/vendor/tabled/src/builder/mod.rs new file mode 100644 index 000000000..9002ba237 --- /dev/null +++ b/vendor/tabled/src/builder/mod.rs @@ -0,0 +1,118 @@ +//! Builder module provides a [`Builder`] type which helps building +//! a [`Table`] dynamically. +//! +//! It also contains [`IndexBuilder`] which can help to build a table with index. +//! +//! # Examples +//! +//! Here's an example of [`IndexBuilder`] usage +//! +#![cfg_attr(feature = "derive", doc = "```")] +#![cfg_attr(not(feature = "derive"), doc = "```ignore")] +//! use tabled::{Table, Tabled, settings::Style}; +//! +//! #[derive(Tabled)] +//! struct Mission { +//! name: &'static str, +//! #[tabled(inline)] +//! status: Status, +//! } +//! +//! #[derive(Tabled)] +//! enum Status { +//! Complete, +//! Started, +//! Ready, +//! Unknown, +//! } +//! +//! let data = [ +//! Mission { name: "Algebra", status: Status::Unknown }, +//! Mission { name: "Apolo", status: Status::Complete }, +//! ]; +//! +//! let mut builder = Table::builder(&data) +//! .index() +//! .column(0) +//! .name(None) +//! .transpose(); +//! +//! let mut table = builder.build(); +//! table.with(Style::modern()); +//! +//! println!("{}", table); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! "┌──────────┬─────────┬───────┐\n", +//! "│ │ Algebra │ Apolo │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Complete │ │ + │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Started │ │ │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Ready │ │ │\n", +//! "├──────────┼─────────┼───────┤\n", +//! "│ Unknown │ + │ │\n", +//! "└──────────┴─────────┴───────┘", +//! ), +//! ) +//! ``` +//! +//! Example when we don't want to show empty data of enum where not all variants are used. +//! +#![cfg_attr(feature = "derive", doc = "```")] +#![cfg_attr(not(feature = "derive"), doc = "```ignore")] +//! use tabled::{Table, Tabled, settings::Style}; +//! +//! #[derive(Tabled)] +//! enum Status { +//! #[tabled(inline)] +//! Complete { +//! started_timestamp: usize, +//! finihsed_timestamp: usize, +//! }, +//! #[tabled(inline)] +//! Started { +//! timestamp: usize, +//! }, +//! Ready, +//! Unknown, +//! } +//! +//! let data = [ +//! Status::Unknown, +//! Status::Complete { started_timestamp: 123, finihsed_timestamp: 234 }, +//! ]; +//! +//! let mut builder = Table::builder(&data); +//! builder.clean(); +//! +//! let table = builder.build() +//! .with(Style::modern()) +//! .to_string(); +//! +//! println!("{}", table); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌───────────────────┬────────────────────┬─────────┐\n", +//! "│ started_timestamp │ finihsed_timestamp │ Unknown │\n", +//! "├───────────────────┼────────────────────┼─────────┤\n", +//! "│ │ │ + │\n", +//! "├───────────────────┼────────────────────┼─────────┤\n", +//! "│ 123 │ 234 │ │\n", +//! "└───────────────────┴────────────────────┴─────────┘", +//! ), +//! ) +//! ``` +//! +//! [`Table`]: crate::Table + +mod index_builder; +mod table_builder; + +pub use index_builder::IndexBuilder; +pub use table_builder::Builder; diff --git a/vendor/tabled/src/builder/table_builder.rs b/vendor/tabled/src/builder/table_builder.rs new file mode 100644 index 000000000..40316494e --- /dev/null +++ b/vendor/tabled/src/builder/table_builder.rs @@ -0,0 +1,506 @@ +use std::iter::FromIterator; + +use crate::{grid::records::vec_records::CellInfo, Table}; + +use super::IndexBuilder; + +/// Builder creates a [`Table`] from dynamic data set. +/// +/// It useful when the amount of columns or rows is not known statically. +/// +/// ```rust +/// use tabled::builder::Builder; +/// +/// let mut builder = Builder::default(); +/// builder.set_header(["index", "measure", "value"]); +/// builder.push_record(["0", "weight", "0.443"]); +/// +/// let table = builder.build(); +/// +/// println!("{}", table); +/// ``` +/// +/// It may be useful to use [`FromIterator`] for building. +/// +/// ```rust +/// use tabled::builder::Builder; +/// use std::iter::FromIterator; +/// +/// let data = vec![ +/// ["column1", "column2"], +/// ["data1", "data2"], +/// ["data3", "data4"], +/// ]; +/// +/// let table = Builder::from_iter(data).build(); +/// +/// println!("{}", table); +/// ``` +#[derive(Debug, Default, Clone)] +pub struct Builder { + /// A list of rows. + data: Vec<Vec<CellInfo<String>>>, + /// A columns row. + columns: Option<Vec<CellInfo<String>>>, + /// A number of columns. + count_columns: usize, + /// A flag that the rows are not consistent. + is_consistent: bool, + /// A content of cells which are created in case rows has different length. + empty_cell_text: Option<String>, +} + +impl Builder { + /// Creates a [`Builder`] instance. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let builder = Builder::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Creates a [`Builder`] instance with a given row capacity. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::with_capacity(2); + /// builder.push_record((0..3).map(|i| i.to_string())); + /// builder.push_record(["i", "surname", "lastname"]); + /// ``` + pub fn with_capacity(capacity: usize) -> Self { + let mut b = Self::new(); + b.data = Vec::with_capacity(capacity); + + b + } + + /// Sets a [`Table`] header. + /// + /// ``` + /// # use tabled::builder::Builder; + /// let mut builder = Builder::default(); + /// builder.set_header((0..3).map(|i| i.to_string())); + /// ``` + pub fn set_header<H, T>(&mut self, columns: H) -> &mut Self + where + H: IntoIterator<Item = T>, + T: Into<String>, + { + let list = create_row(columns, self.count_columns); + + self.update_size(list.len()); + self.columns = Some(list); + + self + } + + /// Sets off a [`Table`] header. + /// + /// If not set its a nop. + /// + /// ```rust + /// use tabled::Table; + /// + /// let data = [("Hello", 1u8, false), ("World", 21u8, true)]; + /// + /// let table = Table::builder(data).build().to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+----+-------+\n\ + /// | &str | u8 | bool |\n\ + /// +-------+----+-------+\n\ + /// | Hello | 1 | false |\n\ + /// +-------+----+-------+\n\ + /// | World | 21 | true |\n\ + /// +-------+----+-------+" + /// ); + /// + /// + /// let mut builder = Table::builder(data); + /// builder.remove_header(); + /// let table = builder.build().to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+----+-------+\n\ + /// | Hello | 1 | false |\n\ + /// +-------+----+-------+\n\ + /// | World | 21 | true |\n\ + /// +-------+----+-------+" + /// ); + /// + /// ``` + pub fn remove_header(&mut self) -> &mut Self { + self.columns = None; + self.count_columns = self.get_size(); + + self + } + + /// Sets a content of cells which are created in case rows has different length. + /// + /// + /// ```rust + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder + /// .set_default_text("undefined") + /// .set_header((0..3).map(|i| i.to_string())) + /// .push_record(["i"]); + /// ``` + pub fn set_default_text<T>(&mut self, text: T) -> &mut Self + where + T: Into<String>, + { + self.empty_cell_text = Some(text.into()); + self + } + + /// Build creates a [`Table`] instance. + /// + /// ```rust + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.set_header(["i", "column1", "column2"]); + /// builder.push_record(["0", "value1", "value2"]); + /// ``` + pub fn build(self) -> Table { + Table::from(self) + } + + /// Add an index to the [`Table`]. + /// + /// Default index is a range 0-N where N is amount of records. + /// + /// # Example + /// + /// ``` + /// use tabled::Table; + /// + /// let table = Table::builder(&["Hello", "World", "!"]).index().build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+---+-------+\n\ + /// | | &str |\n\ + /// +---+-------+\n\ + /// | 0 | Hello |\n\ + /// +---+-------+\n\ + /// | 1 | World |\n\ + /// +---+-------+\n\ + /// | 2 | ! |\n\ + /// +---+-------+" + /// ) + /// ``` + pub fn index(self) -> IndexBuilder { + IndexBuilder::from(self) + } + + /// Adds a row to a [`Table`]. + /// + /// ``` + /// use tabled::builder::Builder; + /// + /// let mut builder = Builder::default(); + /// builder.push_record((0..3).map(|i| i.to_string())); + /// builder.push_record(["i", "surname", "lastname"]); + /// ``` + pub fn push_record<R, T>(&mut self, row: R) -> &mut Self + where + R: IntoIterator<Item = T>, + T: Into<String>, + { + let list = create_row(row, self.count_columns); + + self.update_size(list.len()); + self.data.push(list); + + self + } + + /// Insert a row into a specific position. + /// + /// # Panics + /// + /// Panics if `index > count_rows`. + pub fn insert_record<R>(&mut self, index: usize, record: R) -> bool + where + R: IntoIterator, + R::Item: Into<String>, + { + let list = create_row(record, self.count_columns); + + self.update_size(list.len()); + self.data.insert(index, list); + + true + } + + /// Clean removes empty columns and rows. + /// + /// # Example + /// + /// ``` + /// use tabled::Table; + /// + /// let mut builder = Table::builder(&["Hello", "World", ""]); + /// builder.clean(); + /// + /// let table = builder.build(); + /// + /// assert_eq!( + /// table.to_string(), + /// "+-------+\n\ + /// | &str |\n\ + /// +-------+\n\ + /// | Hello |\n\ + /// +-------+\n\ + /// | World |\n\ + /// +-------+" + /// ) + /// ``` + pub fn clean(&mut self) -> &mut Self { + self.clean_columns(); + self.clean_rows(); + self + } + + /// Set a column size. + /// + /// If it make it lower then it was originally it is considered NOP. + pub fn hint_column_size(&mut self, size: usize) -> &mut Self { + self.count_columns = size; + self.is_consistent = true; + self + } + + /// Returns an amount of columns which would be present in a built table. + pub fn count_columns(&self) -> usize { + self.count_columns + } + + /// Returns an amount of rows which would be present in a built table. + pub fn count_rows(&self) -> usize { + self.data.len() + } + + /// Checks whether a builder contains a header set. + pub fn has_header(&self) -> bool { + self.columns.is_some() + } + + fn clean_columns(&mut self) { + let mut i = 0; + for col in 0..self.count_columns { + let col = col - i; + + let mut is_empty = true; + for row in 0..self.data.len() { + let cell = &self.data[row][col]; + if !cell.as_ref().is_empty() { + is_empty = false; + break; + } + } + + if is_empty { + for row in 0..self.data.len() { + let _ = self.data[row].remove(col); + } + + if let Some(columns) = self.columns.as_mut() { + if columns.len() > col { + let _ = columns.remove(col); + } + } + + i += 1; + } + } + + self.count_columns -= i; + } + + fn clean_rows(&mut self) { + for row in (0..self.data.len()).rev() { + let mut is_empty = true; + for col in 0..self.count_columns { + let cell = &self.data[row][col]; + if !cell.as_ref().is_empty() { + is_empty = false; + break; + } + } + + if is_empty { + let _ = self.data.remove(row); + } + + if row == 0 { + break; + } + } + } + + fn update_size(&mut self, size: usize) { + use std::cmp::Ordering; + + match size.cmp(&self.count_columns) { + Ordering::Less => { + if !self.data.is_empty() { + self.is_consistent = false; + } + } + Ordering::Greater => { + self.count_columns = size; + + if !self.data.is_empty() || self.columns.is_some() { + self.is_consistent = false; + } + } + Ordering::Equal => (), + } + } + + fn get_size(&mut self) -> usize { + let mut max = self.columns.as_ref().map_or(0, Vec::len); + let max_records = self.data.iter().map(Vec::len).max().unwrap_or(0); + max = std::cmp::max(max_records, max); + + max + } + + fn fix_rows(&mut self) { + let empty_cell = self.empty_cell_text.to_owned().unwrap_or_default(); + let empty = CellInfo::new(empty_cell); + + if let Some(header) = self.columns.as_mut() { + if self.count_columns > header.len() { + let count = self.count_columns - header.len(); + append_vec(header, empty.clone(), count); + } + } + + for row in &mut self.data { + if self.count_columns > row.len() { + let count = self.count_columns - row.len(); + append_vec(row, empty.clone(), count); + } + } + } +} + +impl From<Builder> for Vec<Vec<String>> { + fn from(mut builder: Builder) -> Self { + if !builder.is_consistent { + builder.fix_rows(); + } + + if let Some(columns) = builder.columns { + builder.data.insert(0, columns); + } + + builder + .data + .into_iter() + .map(|row| row.into_iter().map(|c| c.into_inner()).collect()) + .collect() + } +} + +impl From<Builder> for Vec<Vec<CellInfo<String>>> { + fn from(mut builder: Builder) -> Self { + if !builder.is_consistent { + builder.fix_rows(); + } + + if let Some(columns) = builder.columns { + builder.data.insert(0, columns); + } + + builder.data + } +} + +impl<R, V> FromIterator<R> for Builder +where + R: IntoIterator<Item = V>, + V: Into<String>, +{ + fn from_iter<T: IntoIterator<Item = R>>(iter: T) -> Self { + let mut builder = Self::default(); + for row in iter { + let _ = builder.push_record(row); + } + + builder + } +} + +impl<D> Extend<D> for Builder +where + D: Into<String>, +{ + fn extend<T: IntoIterator<Item = D>>(&mut self, iter: T) { + let _ = self.push_record(iter); + } +} + +impl From<Vec<Vec<String>>> for Builder { + fn from(data: Vec<Vec<String>>) -> Self { + let count_columns = data.get(0).map_or(0, |row| row.len()); + + let data = data + .into_iter() + .map(|row| row.into_iter().map(CellInfo::new).collect()) + .collect(); + + Self { + data, + count_columns, + columns: None, + is_consistent: false, + empty_cell_text: None, + } + } +} + +impl From<Vec<Vec<CellInfo<String>>>> for Builder { + fn from(data: Vec<Vec<CellInfo<String>>>) -> Self { + let count_columns = data.get(0).map_or(0, |row| row.len()); + + Self { + data, + count_columns, + columns: None, + is_consistent: false, + empty_cell_text: None, + } + } +} + +fn create_row<R, T>(row: R, size: usize) -> Vec<CellInfo<String>> +where + R: IntoIterator<Item = T>, + T: Into<String>, +{ + let mut list = Vec::with_capacity(size); + for text in row { + let text = text.into(); + let info = CellInfo::new(text); + list.push(info); + } + + list +} + +fn append_vec<T: Clone>(v: &mut Vec<T>, value: T, n: usize) { + v.extend((0..n).map(|_| value.clone())); +} diff --git a/vendor/tabled/src/grid/colored_config.rs b/vendor/tabled/src/grid/colored_config.rs new file mode 100644 index 000000000..51a00fbf4 --- /dev/null +++ b/vendor/tabled/src/grid/colored_config.rs @@ -0,0 +1,112 @@ +use std::ops::{Deref, DerefMut}; + +use crate::grid::{ + color::AnsiColor, + config::{Entity, EntityMap, SpannedConfig}, +}; + +/// A spanned configuration plus colors for cells. +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct ColoredConfig { + config: SpannedConfig, + colors: ColorMap, +} + +impl ColoredConfig { + /// Create a new colored config. + pub fn new(config: SpannedConfig) -> Self { + Self { + config, + colors: ColorMap::default(), + } + } + + /// Set a color for a given cell. + /// + /// The outcome is the same as if you'd use [`Format`] and added a color but it'd work only with `color` feature on. + /// While this method works in all contexts. + /// + /// [`Format`]: crate::settings::Format + pub fn set_color(&mut self, pos: Entity, color: AnsiColor<'static>) -> &mut Self { + match self.colors.0.as_mut() { + Some(map) => map.insert(pos, color), + None => { + let mut colors = EntityMap::default(); + colors.insert(pos, color); + self.colors = ColorMap(Some(colors)); + } + } + + self + } + + /// Set a list of colors. + pub fn set_colors(&mut self, colors: EntityMap<AnsiColor<'static>>) -> &mut Self { + self.colors = ColorMap(Some(colors)); + self + } + + /// Remove a color for a given cell. + pub fn remove_color(&mut self, pos: Entity) -> &mut Self { + if let Some(colors) = self.colors.0.as_mut() { + colors.remove(pos); + } + + self + } + + /// Returns a list of colors. + pub fn get_colors(&self) -> &ColorMap { + &self.colors + } + + /// Returns an inner config. + pub fn into_inner(self) -> SpannedConfig { + self.config + } +} + +impl Deref for ColoredConfig { + type Target = SpannedConfig; + + fn deref(&self) -> &Self::Target { + &self.config + } +} + +impl DerefMut for ColoredConfig { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.config + } +} + +impl From<SpannedConfig> for ColoredConfig { + fn from(value: SpannedConfig) -> Self { + Self::new(value) + } +} + +impl AsRef<SpannedConfig> for ColoredConfig { + fn as_ref(&self) -> &SpannedConfig { + &self.config + } +} + +/// A colors structure for [`ColoredConfig`]. +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct ColorMap(Option<EntityMap<AnsiColor<'static>>>); + +impl ColorMap { + /// Checks if any colors is set on. + pub fn is_empty(&self) -> bool { + self.0.is_none() + } +} + +impl crate::grid::colors::Colors for ColorMap { + type Color = AnsiColor<'static>; + + fn get_color(&self, (row, col): (usize, usize)) -> Option<&Self::Color> { + self.0.as_ref().map(|map| map.get(Entity::Cell(row, col))) + } +} diff --git a/vendor/tabled/src/grid/compact_multiline_config.rs b/vendor/tabled/src/grid/compact_multiline_config.rs new file mode 100644 index 000000000..c9056c911 --- /dev/null +++ b/vendor/tabled/src/grid/compact_multiline_config.rs @@ -0,0 +1,211 @@ +use crate::grid::color::StaticColor; +use crate::grid::config::{ + AlignmentHorizontal, AlignmentVertical, Borders, CompactConfig, Indent, Line, Sides, +}; + +/// A [`CompactConfig`] configuration plus vertical alignment. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompactMultilineConfig { + config: CompactConfig, + alignment_vertical: AlignmentVertical, + formatting: Formatting, +} + +impl CompactMultilineConfig { + /// Create a new colored config. + pub fn new(config: CompactConfig) -> Self { + Self::from(config) + } + + /// Set a horizontal alignment. + pub const fn set_alignment_vertical(mut self, alignment: AlignmentVertical) -> Self { + self.alignment_vertical = alignment; + self + } + + /// Get a alignment horizontal. + pub const fn get_alignment_vertical(&self) -> AlignmentVertical { + self.alignment_vertical + } + + /// Set grid margin. + pub const fn set_margin(mut self, margin: Sides<Indent>) -> Self { + self.config = self.config.set_margin(margin); + self + } + + /// Returns a grid margin. + pub const fn get_margin(&self) -> &Sides<Indent> { + self.config.get_margin() + } + + /// Set the [`Borders`] value as correct one. + pub const fn set_borders(mut self, borders: Borders<char>) -> Self { + self.config = self.config.set_borders(borders); + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn set_first_horizontal_line(mut self, line: Line<char>) -> Self { + self.config = self.config.set_first_horizontal_line(line); + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn get_first_horizontal_line(&self) -> Option<Line<char>> { + self.config.get_first_horizontal_line() + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders(&self) -> &Borders<char> { + self.config.get_borders() + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders_color(&self) -> &Borders<StaticColor> { + self.config.get_borders_color() + } + + /// Set a padding to a given cells. + pub const fn set_padding(mut self, padding: Sides<Indent>) -> Self { + self.config = self.config.set_padding(padding); + self + } + + /// Get a padding for a given. + pub const fn get_padding(&self) -> &Sides<Indent> { + self.config.get_padding() + } + + /// Set a horizontal alignment. + pub const fn set_alignment_horizontal(mut self, alignment: AlignmentHorizontal) -> Self { + self.config = self.config.set_alignment_horizontal(alignment); + self + } + + /// Get a alignment horizontal. + pub const fn get_alignment_horizontal(&self) -> AlignmentHorizontal { + self.config.get_alignment_horizontal() + } + + /// Sets colors of border carcass on the grid. + pub const fn set_borders_color(mut self, borders: Borders<StaticColor>) -> Self { + self.config = self.config.set_borders_color(borders); + self + } + + /// Set colors for a margin. + pub const fn set_margin_color(mut self, color: Sides<StaticColor>) -> Self { + self.config = self.config.set_margin_color(color); + self + } + + /// Returns a margin color. + pub const fn get_margin_color(&self) -> Sides<StaticColor> { + self.config.get_margin_color() + } + + /// Set a padding color to all cells. + pub const fn set_padding_color(mut self, color: Sides<StaticColor>) -> Self { + self.config = self.config.set_padding_color(color); + self + } + + /// get a padding color. + pub const fn get_padding_color(&self) -> Sides<StaticColor> { + self.config.get_padding_color() + } + + /// Set formatting. + pub const fn set_formatting(mut self, formatting: Formatting) -> Self { + self.formatting = formatting; + self + } + + /// Get formatting. + pub const fn get_formatting(&self) -> Formatting { + self.formatting + } +} + +impl Default for CompactMultilineConfig { + fn default() -> Self { + Self { + config: Default::default(), + alignment_vertical: AlignmentVertical::Top, + formatting: Formatting::default(), + } + } +} + +impl From<CompactConfig> for CompactMultilineConfig { + fn from(config: CompactConfig) -> Self { + Self { + config, + alignment_vertical: AlignmentVertical::Top, + formatting: Formatting::default(), + } + } +} + +impl AsRef<CompactConfig> for CompactMultilineConfig { + fn as_ref(&self) -> &CompactConfig { + &self.config + } +} + +impl AsMut<CompactConfig> for CompactMultilineConfig { + fn as_mut(&mut self) -> &mut CompactConfig { + &mut self.config + } +} + +#[cfg(feature = "std")] +impl From<CompactMultilineConfig> for crate::grid::config::SpannedConfig { + fn from(compact: CompactMultilineConfig) -> Self { + use crate::grid::config::Entity; + + let mut cfg = crate::grid::config::SpannedConfig::from(compact.config); + cfg.set_alignment_vertical(Entity::Global, compact.alignment_vertical); + cfg.set_formatting(Entity::Global, compact.formatting.into()); + + cfg + } +} + +/// Formatting represent a logic of formatting of a cell. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Formatting { + /// An setting to allow horizontal trim. + pub horizontal_trim: bool, + /// An setting to allow vertical trim. + pub vertical_trim: bool, + /// An setting to allow alignment per line. + pub allow_lines_alignment: bool, +} + +impl Formatting { + /// Creates a new [`Formatting`] structure. + pub fn new(horizontal_trim: bool, vertical_trim: bool, allow_lines_alignment: bool) -> Self { + Self { + horizontal_trim, + vertical_trim, + allow_lines_alignment, + } + } +} + +#[cfg(feature = "std")] +impl From<Formatting> for crate::grid::config::Formatting { + fn from(val: Formatting) -> Self { + crate::grid::config::Formatting { + allow_lines_alignment: val.allow_lines_alignment, + horizontal_trim: val.horizontal_trim, + vertical_trim: val.vertical_trim, + } + } +} diff --git a/vendor/tabled/src/grid/dimension/complete_dimension.rs b/vendor/tabled/src/grid/dimension/complete_dimension.rs new file mode 100644 index 000000000..3147cb27a --- /dev/null +++ b/vendor/tabled/src/grid/dimension/complete_dimension.rs @@ -0,0 +1,136 @@ +use std::borrow::Cow; + +use crate::grid::{ + config::{ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate, SpannedGridDimension}, + records::Records, +}; + +/// CompleteDimension is a [`Dimension`] implementation for a [`Table`] +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct CompleteDimension<'a> { + width: Option<Cow<'a, [usize]>>, + height: Option<Cow<'a, [usize]>>, +} + +impl CompleteDimension<'_> { + /// Checks whether is the dimensions is set. + pub fn is_complete(&self) -> bool { + self.width.is_some() && self.height.is_some() + } + + /// Checks whether is nothing was set. + pub fn is_empty(&self) -> bool { + self.width.is_none() && self.height.is_none() + } + + /// Set column widths. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_widths(&mut self, columns: Vec<usize>) -> bool { + self.width = Some(Cow::Owned(columns)); + + true + } + + /// Set rows heights. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided heights. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_heights(&mut self, rows: Vec<usize>) -> bool { + self.height = Some(Cow::Owned(rows)); + + true + } + + /// Force width estimation. + pub fn clear_width(&mut self) { + self.width = None; + } + + /// Force height estimation. + pub fn clear_height(&mut self) { + self.height = None; + } + + /// Copies a reference from self. + pub fn from_origin(&self) -> CompleteDimension<'_> { + let width = self.width.as_deref().map(Cow::Borrowed); + let height = self.height.as_deref().map(Cow::Borrowed); + + CompleteDimension { width, height } + } +} + +impl Dimension for CompleteDimension<'_> { + fn get_width(&self, column: usize) -> usize { + let width = self + .width + .as_ref() + .expect("It must always be Some at this point"); + + width[column] + } + + fn get_height(&self, row: usize) -> usize { + let height = self + .height + .as_ref() + .expect("It must always be Some at this point"); + + height[row] + } +} + +impl<R: Records> Estimate<R, SpannedConfig> for CompleteDimension<'_> { + fn estimate(&mut self, records: R, cfg: &SpannedConfig) { + match (self.width.is_some(), self.height.is_some()) { + (true, true) => {} + (true, false) => { + self.height = Some(Cow::Owned(SpannedGridDimension::height(records, cfg))); + } + (false, true) => { + self.width = Some(Cow::Owned(SpannedGridDimension::width(records, cfg))); + } + (false, false) => { + let mut dims = SpannedGridDimension::default(); + dims.estimate(records, cfg); + + let (width, height) = dims.get_values(); + self.width = Some(Cow::Owned(width)); + self.height = Some(Cow::Owned(height)); + } + } + } +} + +impl<R: Records> Estimate<R, ColoredConfig> for CompleteDimension<'_> { + fn estimate(&mut self, records: R, cfg: &ColoredConfig) { + match (self.width.is_some(), self.height.is_some()) { + (true, true) => {} + (true, false) => { + self.height = Some(Cow::Owned(SpannedGridDimension::height(records, cfg))); + } + (false, true) => { + self.width = Some(Cow::Owned(SpannedGridDimension::width(records, cfg))); + } + (false, false) => { + let mut dims = SpannedGridDimension::default(); + dims.estimate(records, cfg); + + let (width, height) = dims.get_values(); + self.width = Some(Cow::Owned(width)); + self.height = Some(Cow::Owned(height)); + } + } + } +} diff --git a/vendor/tabled/src/grid/dimension/complete_dimension_vec_records.rs b/vendor/tabled/src/grid/dimension/complete_dimension_vec_records.rs new file mode 100644 index 000000000..ddc806a45 --- /dev/null +++ b/vendor/tabled/src/grid/dimension/complete_dimension_vec_records.rs @@ -0,0 +1,128 @@ +use std::borrow::Cow; + +use papergrid::{ + dimension::spanned_vec_records::SpannedVecRecordsDimension, records::vec_records::VecRecords, +}; + +use crate::grid::{ + config::{ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate}, + records::vec_records::Cell, +}; + +/// CompleteDimension is a [`Dimension`] implementation for a [`Table`] +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct CompleteDimensionVecRecords<'a> { + width: Option<Cow<'a, [usize]>>, + height: Option<Cow<'a, [usize]>>, +} + +impl CompleteDimensionVecRecords<'_> { + /// Checks whether is the dimensions is set. + pub fn is_complete(&self) -> bool { + self.width.is_some() && self.height.is_some() + } + + /// Checks whether is nothing was set. + pub fn is_empty(&self) -> bool { + self.width.is_none() && self.height.is_none() + } + + /// Set column widths. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_widths(&mut self, columns: Vec<usize>) -> bool { + self.width = Some(Cow::Owned(columns)); + + true + } + + /// Set rows heights. + /// + /// In general the method is only considered to be useful to a [`TableOption`]. + /// + /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided heights. + /// + /// [`TableOption`]: crate::settings::TableOption + pub fn set_heights(&mut self, rows: Vec<usize>) -> bool { + self.height = Some(Cow::Owned(rows)); + + true + } + + /// Force width estimation. + pub fn clear_width(&mut self) { + self.width = None; + } + + /// Force height estimation. + pub fn clear_height(&mut self) { + self.height = None; + } + + /// Copies a reference from self. + pub fn from_origin(&self) -> CompleteDimensionVecRecords<'_> { + let width = self.width.as_deref().map(Cow::Borrowed); + let height = self.height.as_deref().map(Cow::Borrowed); + + CompleteDimensionVecRecords { width, height } + } +} + +impl Dimension for CompleteDimensionVecRecords<'_> { + fn get_width(&self, column: usize) -> usize { + let width = self + .width + .as_ref() + .expect("It must always be Some at this point"); + + width[column] + } + + fn get_height(&self, row: usize) -> usize { + let height = self + .height + .as_ref() + .expect("It must always be Some at this point"); + + height[row] + } +} + +impl<T: AsRef<str> + Cell> Estimate<&VecRecords<T>, SpannedConfig> + for CompleteDimensionVecRecords<'_> +{ + fn estimate(&mut self, records: &VecRecords<T>, cfg: &SpannedConfig) { + match (self.width.is_some(), self.height.is_some()) { + (true, true) => {} + (true, false) => { + self.height = Some(Cow::Owned(SpannedVecRecordsDimension::height(records, cfg))); + } + (false, true) => { + self.width = Some(Cow::Owned(SpannedVecRecordsDimension::width(records, cfg))); + } + (false, false) => { + let mut dims = SpannedVecRecordsDimension::default(); + dims.estimate(records, cfg); + + let (width, height) = dims.get_values(); + self.width = Some(Cow::Owned(width)); + self.height = Some(Cow::Owned(height)); + } + } + } +} + +impl<T: AsRef<str> + Cell> Estimate<&VecRecords<T>, ColoredConfig> + for CompleteDimensionVecRecords<'_> +{ + fn estimate(&mut self, records: &VecRecords<T>, cfg: &ColoredConfig) { + self.estimate(records, cfg.as_ref()) + } +} diff --git a/vendor/tabled/src/grid/dimension/const_dimension.rs b/vendor/tabled/src/grid/dimension/const_dimension.rs new file mode 100644 index 000000000..450b1abfe --- /dev/null +++ b/vendor/tabled/src/grid/dimension/const_dimension.rs @@ -0,0 +1,70 @@ +//! Module contains a dimension estimator for [`CompactTable`] +//! +//! [`CompactTable`]: crate::tables::CompactTable + +use crate::grid::dimension::{Dimension, Estimate}; + +/// A constant size dimension or a value dimension. +#[derive(Debug, Clone, Copy)] +pub struct ConstDimension<const COLUMNS: usize, const ROWS: usize> { + height: ConstSize<ROWS>, + width: ConstSize<COLUMNS>, +} + +impl<const COLUMNS: usize, const ROWS: usize> ConstDimension<COLUMNS, ROWS> { + /// Returns a new dimension object with a given estimates. + pub const fn new(width: ConstSize<COLUMNS>, height: ConstSize<ROWS>) -> Self { + Self { width, height } + } +} + +impl<const COLUMNS: usize, const ROWS: usize> Dimension for ConstDimension<COLUMNS, ROWS> { + fn get_width(&self, column: usize) -> usize { + match self.width { + ConstSize::List(list) => list[column], + ConstSize::Value(val) => val, + } + } + + fn get_height(&self, row: usize) -> usize { + match self.height { + ConstSize::List(list) => list[row], + ConstSize::Value(val) => val, + } + } +} + +impl<const COLUMNS: usize, const ROWS: usize> From<ConstDimension<COLUMNS, ROWS>> + for (ConstSize<COLUMNS>, ConstSize<ROWS>) +{ + fn from(value: ConstDimension<COLUMNS, ROWS>) -> Self { + (value.width, value.height) + } +} + +impl<R, D, const COLUMNS: usize, const ROWS: usize> Estimate<R, D> + for ConstDimension<COLUMNS, ROWS> +{ + fn estimate(&mut self, _: R, _: &D) {} +} + +/// Const size represents either a const array values or a single value which responsible for the whole list. +#[derive(Debug, Clone, Copy)] +pub enum ConstSize<const N: usize> { + /// A constant array of estimates. + List([usize; N]), + /// A value which act as a single estimate for all entries. + Value(usize), +} + +impl From<usize> for ConstSize<0> { + fn from(value: usize) -> Self { + ConstSize::Value(value) + } +} + +impl<const N: usize> From<[usize; N]> for ConstSize<N> { + fn from(value: [usize; N]) -> Self { + ConstSize::List(value) + } +} diff --git a/vendor/tabled/src/grid/dimension/mod.rs b/vendor/tabled/src/grid/dimension/mod.rs new file mode 100644 index 000000000..2f67b402f --- /dev/null +++ b/vendor/tabled/src/grid/dimension/mod.rs @@ -0,0 +1,32 @@ +//! Module contains a list of implementations of [`Estimate`] and [`Dimension`]. + +mod const_dimension; +mod pool_table_dimension; + +#[cfg(feature = "std")] +mod complete_dimension; +#[cfg(feature = "std")] +mod complete_dimension_vec_records; +#[cfg(feature = "std")] +mod peekable_dimension; +#[cfg(feature = "std")] +mod static_dimension; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + complete_dimension::CompleteDimension, + complete_dimension_vec_records::CompleteDimensionVecRecords, + peekable_dimension::PeekableDimension, + static_dimension::{DimensionValue, StaticDimension}, +}; +pub use const_dimension::{ConstDimension, ConstSize}; +pub use papergrid::dimension::{Dimension, Estimate}; +pub use pool_table_dimension::{DimensionPriority, PoolTableDimension}; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::dimension::{ + compact::CompactGridDimension, spanned::SpannedGridDimension, + spanned_vec_records::SpannedVecRecordsDimension, +}; diff --git a/vendor/tabled/src/grid/dimension/peekable_dimension.rs b/vendor/tabled/src/grid/dimension/peekable_dimension.rs new file mode 100644 index 000000000..1ba6fda21 --- /dev/null +++ b/vendor/tabled/src/grid/dimension/peekable_dimension.rs @@ -0,0 +1,335 @@ +use papergrid::records::vec_records::{CellInfo, VecRecords}; + +use crate::grid::{ + config::SpannedConfig, + dimension::{Dimension, Estimate}, + records::Records, +}; + +/// PeekableDimension is a [`Dimension`] implementation for a [`Table`] +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, Clone)] +pub struct PeekableDimension { + width: Vec<usize>, + height: Vec<usize>, +} + +impl PeekableDimension { + /// Calculates height of rows. + pub fn height<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + estimation::build_height(records, cfg) + } + + /// Calculates width of columns. + pub fn width<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + estimation::build_width(records, cfg) + } + + /// Return width and height lists. + pub fn get_values(self) -> (Vec<usize>, Vec<usize>) { + (self.width, self.height) + } +} + +impl Dimension for PeekableDimension { + fn get_width(&self, column: usize) -> usize { + self.width[column] + } + + fn get_height(&self, row: usize) -> usize { + self.height[row] + } +} + +impl<T> Estimate<&VecRecords<CellInfo<T>>, SpannedConfig> for PeekableDimension +where + T: AsRef<str>, +{ + fn estimate(&mut self, records: &VecRecords<CellInfo<T>>, cfg: &SpannedConfig) { + let (width, height) = estimation::build_dimensions(records, cfg); + self.width = width; + self.height = height; + } +} + +mod estimation { + use core::cmp::{max, Ordering}; + use std::collections::HashMap; + + use papergrid::{ + config::Position, + records::vec_records::{Cell, CellInfo, VecRecords}, + }; + + use super::*; + + pub(super) fn build_dimensions<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> (Vec<usize>, Vec<usize>) { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut heights = vec![]; + + let mut vspans = HashMap::new(); + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let height = cell.count_lines(); + let width = cell.width(); + + let pad = cfg.get_padding(pos.into()); + let width = width + pad.left.size + pad.right.size; + let height = height + pad.top.size + pad.bottom.size; + + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + let _ = vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + let _ = hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + let count_rows = heights.len(); + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + adjust_hspans(cfg, count_rows, &hspans, &mut heights); + + (widths, heights) + } + + fn adjust_hspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + heights: &mut [usize], + ) { + if spans.is_empty() { + return; + } + + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { + Ordering::Equal => acol.cmp(bcol), + ord => ord, + }); + + for ((row, _), (span, height)) in spans_ordered { + adjust_row_range(cfg, height, len, row, row + span, heights); + } + } + + fn adjust_row_range( + cfg: &SpannedConfig, + max_span_height: usize, + len: usize, + start: usize, + end: usize, + heights: &mut [usize], + ) { + let range_height = range_height(cfg, len, start, end, heights); + if range_height >= max_span_height { + return; + } + + inc_range(heights, max_span_height - range_height, start, end); + } + + fn range_height( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + heights: &[usize], + ) -> usize { + let count_borders = count_horizontal_borders(cfg, len, start, end); + let range_height = heights[start..end].iter().sum::<usize>(); + count_borders + range_height + } + + fn count_horizontal_borders( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + ) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_horizontal(i, len)) + .count() + } + + fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) { + if list.is_empty() { + return; + } + + let span = end - start; + let one = size / span; + let rest = size - span * one; + + let mut i = start; + while i < end { + if i == start { + list[i] += one + rest; + } else { + list[i] += one; + } + + i += 1; + } + } + + fn adjust_vspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + widths: &mut [usize], + ) { + if spans.is_empty() { + return; + } + + // The overall width distribution will be different depend on the order. + // + // We sort spans in order to prioritize the smaller spans first. + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) { + Ordering::Equal => a.0.cmp(&b.0), + o => o, + }); + + for ((_, col), (span, width)) in spans_ordered { + adjust_column_range(cfg, width, len, col, col + span, widths); + } + } + + fn adjust_column_range( + cfg: &SpannedConfig, + max_span_width: usize, + len: usize, + start: usize, + end: usize, + widths: &mut [usize], + ) { + let range_width = range_width(cfg, len, start, end, widths); + if range_width >= max_span_width { + return; + } + + inc_range(widths, max_span_width - range_width, start, end); + } + + fn range_width( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + widths: &[usize], + ) -> usize { + let count_borders = count_vertical_borders(cfg, len, start, end); + let range_width = widths[start..end].iter().sum::<usize>(); + count_borders + range_width + } + + fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, len)) + .count() + } + + pub(super) fn build_height<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + let mut heights = vec![]; + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let height = cell.count_lines(); + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + let _ = hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + adjust_hspans(cfg, heights.len(), &hspans, &mut heights); + + heights + } + + pub(super) fn build_width<T: AsRef<str>>( + records: &VecRecords<CellInfo<T>>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut vspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let width = cell.width(); + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + let _ = vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + } + } + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + + widths + } +} diff --git a/vendor/tabled/src/grid/dimension/pool_table_dimension.rs b/vendor/tabled/src/grid/dimension/pool_table_dimension.rs new file mode 100644 index 000000000..9909c661d --- /dev/null +++ b/vendor/tabled/src/grid/dimension/pool_table_dimension.rs @@ -0,0 +1,36 @@ +/// PoolTableDimension is a dimension resolve strategy for [`PoolTable`] +/// +/// [`PoolTable`]: crate::tables::PoolTable +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct PoolTableDimension { + width: DimensionPriority, + height: DimensionPriority, +} + +impl PoolTableDimension { + /// Creates a new object. + pub fn new(width: DimensionPriority, height: DimensionPriority) -> Self { + Self { width, height } + } + + /// Return a width priority. + pub fn width(&self) -> DimensionPriority { + self.width + } + + /// Return a height priority. + pub fn height(&self) -> DimensionPriority { + self.height + } +} + +/// A control of width/height logic for situations where we must increase some cell to align columns/row. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum DimensionPriority { + /// Increase first cell width/height in a row/column. + First, + /// Increase last cell width/height in a row/column. + Last, + /// Increase cells width/height 1 by 1 in a row/column. + List, +} diff --git a/vendor/tabled/src/grid/dimension/static_dimension.rs b/vendor/tabled/src/grid/dimension/static_dimension.rs new file mode 100644 index 000000000..f9474f212 --- /dev/null +++ b/vendor/tabled/src/grid/dimension/static_dimension.rs @@ -0,0 +1,63 @@ +use crate::grid::dimension::{Dimension, Estimate}; + +/// A constant dimension. +#[derive(Debug, Clone)] +pub struct StaticDimension { + width: DimensionValue, + height: DimensionValue, +} + +impl StaticDimension { + /// Creates a constant dimension. + pub fn new(width: DimensionValue, height: DimensionValue) -> Self { + Self { width, height } + } +} + +impl From<StaticDimension> for (DimensionValue, DimensionValue) { + fn from(value: StaticDimension) -> Self { + (value.width, value.height) + } +} + +impl Dimension for StaticDimension { + fn get_width(&self, column: usize) -> usize { + self.width.get(column) + } + + fn get_height(&self, row: usize) -> usize { + self.height.get(row) + } +} + +impl<R, C> Estimate<R, C> for StaticDimension { + fn estimate(&mut self, _: R, _: &C) {} +} + +/// A dimension value. +#[derive(Debug, Clone)] +pub enum DimensionValue { + /// Const width value. + Exact(usize), + /// A list of width values for columns. + List(Vec<usize>), + /// A list of width values for columns and a value for the rest. + Partial(Vec<usize>, usize), +} + +impl DimensionValue { + /// Get a width by column. + pub fn get(&self, col: usize) -> usize { + match self { + DimensionValue::Exact(val) => *val, + DimensionValue::List(cols) => cols[col], + DimensionValue::Partial(cols, val) => { + if cols.len() > col { + cols[col] + } else { + *val + } + } + } + } +} diff --git a/vendor/tabled/src/grid/mod.rs b/vendor/tabled/src/grid/mod.rs new file mode 100644 index 000000000..cdd8c55c0 --- /dev/null +++ b/vendor/tabled/src/grid/mod.rs @@ -0,0 +1,48 @@ +//! Module is responsible for tables underlyign grid. +//! +//! It might be used when implementing your own [`TableOption`] and [`CellOption`]. +//! +//! [`TableOption`]: crate::settings::TableOption +//! [`CellOption`]: crate::settings::CellOption +#[cfg(feature = "std")] +mod colored_config; + +mod compact_multiline_config; + +pub mod dimension; +pub mod records; + +pub use papergrid::color; +pub use papergrid::colors; +pub use papergrid::util; + +pub mod config { + //! Module contains a list of configs for varios tables/grids. + + pub use papergrid::config::{ + compact::CompactConfig, AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, + EntityIterator, Indent, Line, Position, Sides, + }; + + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub use papergrid::config::spanned::{ + EntityMap, Formatting, HorizontalLine, Offset, SpannedConfig, VerticalLine, + }; + + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub use super::colored_config::{ColorMap, ColoredConfig}; + + pub use super::compact_multiline_config::CompactMultilineConfig; +} + +pub use papergrid::grid::compact::CompactGrid; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::grid::iterable::Grid; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::grid::peekable::PeekableGrid; diff --git a/vendor/tabled/src/grid/records/empty_records.rs b/vendor/tabled/src/grid/records/empty_records.rs new file mode 100644 index 000000000..77ebc812d --- /dev/null +++ b/vendor/tabled/src/grid/records/empty_records.rs @@ -0,0 +1,41 @@ +//! An empty [`Records`] implementation. + +use core::iter::{repeat, Repeat, Take}; + +use super::Records; + +/// Empty representation of [`Records`]. +#[derive(Debug, Default, Clone)] +pub struct EmptyRecords { + rows: usize, + cols: usize, +} + +impl EmptyRecords { + /// Constructs an empty representation of [`Records`] with a given shape. + pub fn new(rows: usize, cols: usize) -> Self { + Self { rows, cols } + } +} + +impl From<(usize, usize)> for EmptyRecords { + fn from((count_rows, count_columns): (usize, usize)) -> Self { + Self::new(count_rows, count_columns) + } +} + +impl Records for EmptyRecords { + type Iter = Take<Repeat<Take<Repeat<&'static str>>>>; + + fn iter_rows(self) -> Self::Iter { + repeat(repeat("").take(self.cols)).take(self.rows) + } + + fn count_columns(&self) -> usize { + self.cols + } + + fn hint_count_rows(&self) -> Option<usize> { + Some(self.rows) + } +} diff --git a/vendor/tabled/src/grid/records/into_records/buf_records.rs b/vendor/tabled/src/grid/records/into_records/buf_records.rs new file mode 100644 index 000000000..2db3e45bf --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/buf_records.rs @@ -0,0 +1,213 @@ +//! A module contains [`BufRows`] and [`BufColumns`] iterators. +//! +//! Almoust always they both can be used interchangeably but [`BufRows`] is supposed to be lighter cause it +//! does not reads columns. + +use crate::grid::records::IntoRecords; + +use super::either_string::EitherString; + +/// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. +/// So it can be checking before hand. +#[derive(Debug)] +pub struct BufRows<I, T> { + iter: I, + buf: Vec<T>, +} + +impl BufRows<(), ()> { + /// Creates a new [`BufRows`] structure, filling the buffer. + pub fn new<I: IntoRecords>( + records: I, + sniff: usize, + ) -> BufRows<<I::IterRows as IntoIterator>::IntoIter, I::IterColumns> { + let mut buf = vec![]; + + let mut iter = records.iter_rows().into_iter(); + for _ in 0..sniff { + match iter.next() { + Some(row) => buf.push(row), + None => break, + } + } + + BufRows { iter, buf } + } +} + +impl<I, T> BufRows<I, T> { + /// Returns a slice of a record buffer. + pub fn as_slice(&self) -> &[T] { + &self.buf + } +} + +impl<I, T> From<BufRows<I, T>> for BufColumns<I> +where + T: IntoIterator, + T::Item: AsRef<str>, +{ + fn from(value: BufRows<I, T>) -> Self { + let buf = value + .buf + .into_iter() + .map(|row| row.into_iter().map(|s| s.as_ref().to_string()).collect()) + .collect(); + + BufColumns { + iter: value.iter, + buf, + } + } +} + +impl<I, T> IntoRecords for BufRows<I, T> +where + I: Iterator<Item = T>, + T: IntoIterator, + T::Item: AsRef<str>, +{ + type Cell = T::Item; + type IterColumns = T; + type IterRows = BufRowIter<I, T>; + + fn iter_rows(self) -> Self::IterRows { + BufRowIter { + buf: self.buf.into_iter(), + iter: self.iter, + } + } +} + +/// Buffered [`Iterator`]. +#[derive(Debug)] +pub struct BufRowIter<I, T> { + buf: std::vec::IntoIter<T>, + iter: I, +} + +impl<I, T> Iterator for BufRowIter<I, T> +where + I: Iterator<Item = T>, + T: IntoIterator, + T::Item: AsRef<str>, +{ + type Item = T; + + fn next(&mut self) -> Option<Self::Item> { + match self.buf.next() { + Some(i) => Some(i), + None => self.iter.next(), + } + } +} + +/// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. +/// So it can be checking before hand. +/// +/// In contrast to [`BufRows`] it keeps records by columns. +#[derive(Debug)] +pub struct BufColumns<I> { + iter: I, + buf: Vec<Vec<String>>, +} + +impl BufColumns<()> { + /// Creates new [`BufColumns`] structure, filling the buffer. + pub fn new<I: IntoRecords>( + records: I, + sniff: usize, + ) -> BufColumns<<I::IterRows as IntoIterator>::IntoIter> { + let mut buf = vec![]; + + let mut iter = records.iter_rows().into_iter(); + for _ in 0..sniff { + match iter.next() { + Some(row) => { + let row = row + .into_iter() + .map(|cell| cell.as_ref().to_string()) + .collect::<Vec<_>>(); + buf.push(row) + } + None => break, + } + } + + BufColumns { iter, buf } + } +} + +impl<I> BufColumns<I> { + /// Returns a slice of a keeping buffer. + pub fn as_slice(&self) -> &[Vec<String>] { + &self.buf + } +} + +impl<I> IntoRecords for BufColumns<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Cell = EitherString<<I::Item as IntoIterator>::Item>; + type IterColumns = EitherRowIterator<<I::Item as IntoIterator>::IntoIter>; + type IterRows = BufColumnIter<I>; + + fn iter_rows(self) -> Self::IterRows { + BufColumnIter { + buf: self.buf.into_iter(), + iter: self.iter, + } + } +} + +/// A row iterator for [`BufColumns`] +#[derive(Debug)] +pub struct BufColumnIter<I> { + buf: std::vec::IntoIter<Vec<String>>, + iter: I, +} + +impl<I> Iterator for BufColumnIter<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = EitherRowIterator<<I::Item as IntoIterator>::IntoIter>; + + fn next(&mut self) -> Option<Self::Item> { + match self.buf.next() { + Some(i) => Some(EitherRowIterator::Owned(i.into_iter())), + None => self + .iter + .next() + .map(|i| EitherRowIterator::Some(i.into_iter())), + } + } +} + +/// An iterator over some iterator or allocated buffer. +#[derive(Debug)] +pub enum EitherRowIterator<I> { + /// Allocated iterator. + Owned(std::vec::IntoIter<String>), + /// Given iterator. + Some(I), +} + +impl<I> Iterator for EitherRowIterator<I> +where + I: Iterator, +{ + type Item = EitherString<I::Item>; + + fn next(&mut self) -> Option<Self::Item> { + match self { + EitherRowIterator::Owned(iter) => iter.next().map(EitherString::Owned), + EitherRowIterator::Some(iter) => iter.next().map(EitherString::Some), + } + } +} diff --git a/vendor/tabled/src/grid/records/into_records/either_string.rs b/vendor/tabled/src/grid/records/into_records/either_string.rs new file mode 100644 index 000000000..f4d97290d --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/either_string.rs @@ -0,0 +1,22 @@ +//! A module with a utility enum [`EitherString`]. + +/// Either allocated string or some type which can be used as a string. +#[derive(Debug)] +pub enum EitherString<T> { + /// Allocated string. + Owned(String), + /// Something which can be used as a string. + Some(T), +} + +impl<T> AsRef<str> for EitherString<T> +where + T: AsRef<str>, +{ + fn as_ref(&self) -> &str { + match self { + EitherString::Owned(s) => s.as_ref(), + EitherString::Some(s) => s.as_ref(), + } + } +} diff --git a/vendor/tabled/src/grid/records/into_records/limit_column_records.rs b/vendor/tabled/src/grid/records/into_records/limit_column_records.rs new file mode 100644 index 000000000..89b2b89ed --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/limit_column_records.rs @@ -0,0 +1,82 @@ +//! The module contains [`LimitColumns`] records iterator. + +use crate::grid::records::IntoRecords; + +/// An iterator which limits amount of columns. +#[derive(Debug)] +pub struct LimitColumns<I> { + records: I, + limit: usize, +} + +impl LimitColumns<()> { + /// Creates new [`LimitColumns`]. + pub fn new<I: IntoRecords>(records: I, limit: usize) -> LimitColumns<I> { + LimitColumns { records, limit } + } +} + +impl<I> IntoRecords for LimitColumns<I> +where + I: IntoRecords, +{ + type Cell = I::Cell; + type IterColumns = LimitColumnsColumnsIter<<I::IterColumns as IntoIterator>::IntoIter>; + type IterRows = LimitColumnsIter<<I::IterRows as IntoIterator>::IntoIter>; + + fn iter_rows(self) -> Self::IterRows { + LimitColumnsIter { + iter: self.records.iter_rows().into_iter(), + limit: self.limit, + } + } +} + +/// An iterator over rows for [`LimitColumns`]. +#[derive(Debug)] +pub struct LimitColumnsIter<I> { + iter: I, + limit: usize, +} + +impl<I> Iterator for LimitColumnsIter<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = LimitColumnsColumnsIter<<I::Item as IntoIterator>::IntoIter>; + + fn next(&mut self) -> Option<Self::Item> { + let iter = self.iter.next()?; + Some(LimitColumnsColumnsIter { + iter: iter.into_iter(), + limit: self.limit, + }) + } +} + +/// An iterator over columns for [`LimitColumns`]. +#[derive(Debug)] +pub struct LimitColumnsColumnsIter<I> { + iter: I, + limit: usize, +} + +impl<I> Iterator for LimitColumnsColumnsIter<I> +where + I: Iterator, + I::Item: AsRef<str>, +{ + type Item = I::Item; + + fn next(&mut self) -> Option<Self::Item> { + if self.limit == 0 { + return None; + } + + self.limit -= 1; + + self.iter.next() + } +} diff --git a/vendor/tabled/src/grid/records/into_records/limit_row_records.rs b/vendor/tabled/src/grid/records/into_records/limit_row_records.rs new file mode 100644 index 000000000..a461c6682 --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/limit_row_records.rs @@ -0,0 +1,59 @@ +//! The module contains [`LimitRows`] records iterator. + +use crate::grid::records::IntoRecords; + +/// [`LimitRows`] is an records iterator which limits amount of rows. +#[derive(Debug)] +pub struct LimitRows<I> { + records: I, + limit: usize, +} + +impl LimitRows<()> { + /// Creates new [`LimitRows`] iterator. + pub fn new<I: IntoRecords>(records: I, limit: usize) -> LimitRows<I> { + LimitRows { records, limit } + } +} + +impl<I> IntoRecords for LimitRows<I> +where + I: IntoRecords, +{ + type Cell = I::Cell; + type IterColumns = I::IterColumns; + type IterRows = LimitRowsIter<<I::IterRows as IntoIterator>::IntoIter>; + + fn iter_rows(self) -> Self::IterRows { + LimitRowsIter { + iter: self.records.iter_rows().into_iter(), + limit: self.limit, + } + } +} + +/// A rows iterator for [`LimitRows`] +#[derive(Debug)] +pub struct LimitRowsIter<I> { + iter: I, + limit: usize, +} + +impl<I> Iterator for LimitRowsIter<I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = I::Item; + + fn next(&mut self) -> Option<Self::Item> { + if self.limit == 0 { + return None; + } + + self.limit -= 1; + + self.iter.next() + } +} diff --git a/vendor/tabled/src/grid/records/into_records/mod.rs b/vendor/tabled/src/grid/records/into_records/mod.rs new file mode 100644 index 000000000..0a52c41c1 --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/mod.rs @@ -0,0 +1,26 @@ +//! The module contains a list of helpers for [`IntoRecords`] +//! +//! [`IntoRecords`]: crate::grid::records::IntoRecords + +pub mod limit_column_records; +pub mod limit_row_records; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod buf_records; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod either_string; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod truncate_records; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use buf_records::{BufColumns, BufRows}; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use truncate_records::TruncateContent; + +pub use limit_column_records::LimitColumns; +pub use limit_row_records::LimitRows; diff --git a/vendor/tabled/src/grid/records/into_records/truncate_records.rs b/vendor/tabled/src/grid/records/into_records/truncate_records.rs new file mode 100644 index 000000000..17e7e533e --- /dev/null +++ b/vendor/tabled/src/grid/records/into_records/truncate_records.rs @@ -0,0 +1,136 @@ +//! The module contains [`TruncateContent`] records iterator. + +use std::borrow::Cow; + +use crate::{ + grid::records::IntoRecords, grid::util::string::string_width_multiline, + settings::width::Truncate, +}; + +use super::either_string::EitherString; + +/// A records iterator which truncates all cells to a given width. +#[derive(Debug)] +pub struct TruncateContent<'a, I> { + records: I, + width: ExactValue<'a>, +} + +impl TruncateContent<'_, ()> { + /// Creates new [`TruncateContent`] object. + pub fn new<I: IntoRecords>(records: I, width: ExactValue<'_>) -> TruncateContent<'_, I> { + TruncateContent { records, width } + } +} + +impl<'a, I> IntoRecords for TruncateContent<'a, I> +where + I: IntoRecords, +{ + type Cell = EitherString<I::Cell>; + type IterColumns = TruncateContentColumnsIter<'a, <I::IterColumns as IntoIterator>::IntoIter>; + type IterRows = TruncateContentIter<'a, <I::IterRows as IntoIterator>::IntoIter>; + + fn iter_rows(self) -> Self::IterRows { + TruncateContentIter { + iter: self.records.iter_rows().into_iter(), + width: self.width.clone(), + } + } +} + +/// A row iterator for [`TruncateContent`]. +#[derive(Debug)] +pub struct TruncateContentIter<'a, I> { + iter: I, + width: ExactValue<'a>, +} + +impl<'a, I> Iterator for TruncateContentIter<'a, I> +where + I: Iterator, + I::Item: IntoIterator, + <I::Item as IntoIterator>::Item: AsRef<str>, +{ + type Item = TruncateContentColumnsIter<'a, <I::Item as IntoIterator>::IntoIter>; + + fn next(&mut self) -> Option<Self::Item> { + let iter = self.iter.next()?; + Some(TruncateContentColumnsIter { + iter: iter.into_iter(), + current: 0, + width: self.width.clone(), + }) + } +} + +/// A column iterator for [`TruncateContent`]. +#[derive(Debug)] +pub struct TruncateContentColumnsIter<'a, I> { + iter: I, + width: ExactValue<'a>, + current: usize, +} + +impl<I> Iterator for TruncateContentColumnsIter<'_, I> +where + I: Iterator, + I::Item: AsRef<str>, +{ + type Item = EitherString<I::Item>; + + fn next(&mut self) -> Option<Self::Item> { + let s = self.iter.next()?; + + let width = self.width.get(self.current); + self.current += 1; + + let text_width = string_width_multiline(s.as_ref()); + if text_width <= width { + return Some(EitherString::Some(s)); + } + + let text = Truncate::truncate_text(s.as_ref(), width); + let text = text.into_owned(); + let text = EitherString::Owned(text); + + Some(text) + } +} + +/// A width value. +#[derive(Debug, Clone)] +pub enum ExactValue<'a> { + /// Const width value. + Exact(usize), + /// A list of width values for columns. + List(Cow<'a, [usize]>), +} + +impl<'a> From<&'a [usize]> for ExactValue<'a> { + fn from(value: &'a [usize]) -> Self { + Self::List(value.into()) + } +} + +impl From<Vec<usize>> for ExactValue<'_> { + fn from(value: Vec<usize>) -> Self { + Self::List(value.into()) + } +} + +impl From<usize> for ExactValue<'_> { + fn from(value: usize) -> Self { + Self::Exact(value) + } +} + +impl ExactValue<'_> { + /// Get a width by column. + pub fn get(&self, col: usize) -> usize { + match self { + ExactValue::Exact(val) => *val, + ExactValue::List(cols) => cols[col], + } + } +} diff --git a/vendor/tabled/src/grid/records/mod.rs b/vendor/tabled/src/grid/records/mod.rs new file mode 100644 index 000000000..494002346 --- /dev/null +++ b/vendor/tabled/src/grid/records/mod.rs @@ -0,0 +1,19 @@ +//! The module contains [`Records`], [`ExactRecords`], [`RecordsMut`], [`Resizable`] traits +//! and its implementations. +//! +//! Also it provies a list of helpers for a user built [`Records`] via [`into_records`]. + +mod empty_records; +mod records_mut; +mod resizable; + +pub mod into_records; + +pub use empty_records::EmptyRecords; +pub use papergrid::records::{ExactRecords, IntoRecords, IterRecords, PeekableRecords, Records}; +pub use records_mut::RecordsMut; +pub use resizable::Resizable; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use papergrid::records::vec_records; diff --git a/vendor/tabled/src/grid/records/records_mut.rs b/vendor/tabled/src/grid/records/records_mut.rs new file mode 100644 index 000000000..38c42d2c7 --- /dev/null +++ b/vendor/tabled/src/grid/records/records_mut.rs @@ -0,0 +1,34 @@ +use crate::grid::config::Position; +#[cfg(feature = "std")] +use crate::grid::records::vec_records::{CellInfo, VecRecords}; + +/// A [`Records`] representation which can modify cell by (row, column) index. +/// +/// [`Records`]: crate::grid::records::Records +pub trait RecordsMut<Text> { + /// Sets a text to a given cell by index. + fn set(&mut self, pos: Position, text: Text); +} + +impl<T, Text> RecordsMut<Text> for &'_ mut T +where + T: RecordsMut<Text>, +{ + fn set(&mut self, pos: Position, text: Text) { + T::set(self, pos, text) + } +} + +#[cfg(feature = "std")] +impl RecordsMut<String> for VecRecords<CellInfo<String>> { + fn set(&mut self, (row, col): Position, text: String) { + self[row][col] = CellInfo::new(text); + } +} + +#[cfg(feature = "std")] +impl RecordsMut<&str> for VecRecords<CellInfo<String>> { + fn set(&mut self, (row, col): Position, text: &str) { + self[row][col] = CellInfo::new(text.to_string()); + } +} diff --git a/vendor/tabled/src/grid/records/resizable.rs b/vendor/tabled/src/grid/records/resizable.rs new file mode 100644 index 000000000..29ab38038 --- /dev/null +++ b/vendor/tabled/src/grid/records/resizable.rs @@ -0,0 +1,217 @@ +use papergrid::config::Position; + +#[cfg(feature = "std")] +use crate::grid::records::vec_records::VecRecords; + +/// A records representation which can be modified by moving rows/columns around. +pub trait Resizable { + /// Swap cells with one another. + fn swap(&mut self, lhs: Position, rhs: Position); + /// Swap rows with one another. + fn swap_row(&mut self, lhs: usize, rhs: usize); + /// Swap columns with one another. + fn swap_column(&mut self, lhs: usize, rhs: usize); + /// Adds a new row to a data set. + fn push_row(&mut self); + /// Adds a new column to a data set. + fn push_column(&mut self); + /// Removes a row from a data set by index. + fn remove_row(&mut self, row: usize); + /// Removes a column from a data set by index. + fn remove_column(&mut self, column: usize); + /// Inserts a row at index. + fn insert_row(&mut self, row: usize); + /// Inserts column at index. + fn insert_column(&mut self, column: usize); +} + +impl<T> Resizable for &'_ mut T +where + T: Resizable, +{ + fn swap(&mut self, lhs: Position, rhs: Position) { + T::swap(self, lhs, rhs) + } + + fn swap_row(&mut self, lhs: usize, rhs: usize) { + T::swap_row(self, lhs, rhs) + } + + fn swap_column(&mut self, lhs: usize, rhs: usize) { + T::swap_column(self, lhs, rhs) + } + + fn push_row(&mut self) { + T::push_row(self) + } + + fn push_column(&mut self) { + T::push_column(self) + } + + fn remove_row(&mut self, row: usize) { + T::remove_row(self, row) + } + + fn remove_column(&mut self, column: usize) { + T::remove_column(self, column) + } + + fn insert_row(&mut self, row: usize) { + T::insert_row(self, row) + } + + fn insert_column(&mut self, column: usize) { + T::insert_column(self, column) + } +} + +#[cfg(feature = "std")] +impl<T> Resizable for Vec<Vec<T>> +where + T: Default + Clone, +{ + fn swap(&mut self, lhs: Position, rhs: Position) { + if lhs == rhs { + return; + } + + let t = std::mem::take(&mut self[lhs.0][lhs.1]); + let t = std::mem::replace(&mut self[rhs.0][rhs.1], t); + let _ = std::mem::replace(&mut self[lhs.0][lhs.1], t); + } + + fn swap_row(&mut self, lhs: usize, rhs: usize) { + let t = std::mem::take(&mut self[lhs]); + let t = std::mem::replace(&mut self[rhs], t); + let _ = std::mem::replace(&mut self[lhs], t); + } + + fn swap_column(&mut self, lhs: usize, rhs: usize) { + for row in self.iter_mut() { + row.swap(lhs, rhs); + } + } + + fn push_row(&mut self) { + let count_columns = self.get(0).map(|l| l.len()).unwrap_or(0); + self.push(vec![T::default(); count_columns]); + } + + fn push_column(&mut self) { + for row in self.iter_mut() { + row.push(T::default()); + } + } + + fn remove_row(&mut self, row: usize) { + let _ = self.remove(row); + } + + fn remove_column(&mut self, column: usize) { + for row in self.iter_mut() { + let _ = row.remove(column); + } + } + + fn insert_row(&mut self, row: usize) { + let count_columns = self.get(0).map(|l| l.len()).unwrap_or(0); + self.insert(row, vec![T::default(); count_columns]); + } + + fn insert_column(&mut self, column: usize) { + for row in self { + row.insert(column, T::default()); + } + } +} + +#[cfg(feature = "std")] +impl<T> Resizable for VecRecords<T> +where + T: Default + Clone, +{ + fn swap(&mut self, lhs: Position, rhs: Position) { + if lhs == rhs { + return; + } + + let t = std::mem::take(&mut self[lhs.0][lhs.1]); + let t = std::mem::replace(&mut self[rhs.0][rhs.1], t); + let _ = std::mem::replace(&mut self[lhs.0][lhs.1], t); + } + + fn swap_row(&mut self, lhs: usize, rhs: usize) { + let t = std::mem::take(&mut self[lhs]); + let t = std::mem::replace(&mut self[rhs], t); + let _ = std::mem::replace(&mut self[lhs], t); + } + + fn swap_column(&mut self, lhs: usize, rhs: usize) { + for row in self.iter_mut() { + row.swap(lhs, rhs); + } + } + + fn push_row(&mut self) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + let count_columns = data.get(0).map(|l| l.len()).unwrap_or(0); + data.push(vec![T::default(); count_columns]); + + *self = VecRecords::new(data); + } + + fn push_column(&mut self) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + for row in &mut data { + row.push(T::default()); + } + + *self = VecRecords::new(data); + } + + fn remove_row(&mut self, row: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + let _ = data.remove(row); + + *self = VecRecords::new(data); + } + + fn remove_column(&mut self, column: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + for row in &mut data { + let _ = row.remove(column); + } + + *self = VecRecords::new(data); + } + + fn insert_row(&mut self, row: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + let count_columns = data.get(0).map(|l| l.len()).unwrap_or(0); + data.insert(row, vec![T::default(); count_columns]); + + *self = VecRecords::new(data); + } + + fn insert_column(&mut self, column: usize) { + let records = std::mem::replace(self, VecRecords::new(vec![])); + let mut data: Vec<Vec<_>> = records.into(); + + for row in &mut data { + row.insert(column, T::default()); + } + + *self = VecRecords::new(data); + } +} diff --git a/vendor/tabled/src/lib.rs b/vendor/tabled/src/lib.rs new file mode 100644 index 000000000..fb008b302 --- /dev/null +++ b/vendor/tabled/src/lib.rs @@ -0,0 +1,490 @@ +//! An easy to use library for pretty print tables of Rust `struct`s and `enum`s. +//! +//! The library supports different approaches of table building. +//! You can use [`Tabled`] trait if the data type is known. +//! Or you can use [`Builder`] to construct the table from scratch. +//! +//! ## Usage +//! +//! If you want to build a table for your custom type. +//! A starting point is to a anotate your type with `#[derive(Tabled)]`. +//! +//! Then to provide your collection to [`Table::new`] and you will be set to render table. +//! +#![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] +#![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] +//! use tabled::{Tabled, Table}; +//! +//! #[derive(Tabled)] +//! struct Language { +//! name: &'static str, +//! designed_by: &'static str, +//! invented_year: usize, +//! } +//! +//! let languages = vec![ +//! Language{ +//! name: "C", +//! designed_by: "Dennis Ritchie", +//! invented_year: 1972 +//! }, +//! Language{ +//! name: "Rust", +//! designed_by: "Graydon Hoare", +//! invented_year: 2010 +//! }, +//! Language{ +//! name: "Go", +//! designed_by: "Rob Pike", +//! invented_year: 2009 +//! }, +//! ]; +//! +//! let table = Table::new(languages).to_string(); +//! +//! let expected = "+------+----------------+---------------+\n\ +//! | name | designed_by | invented_year |\n\ +//! +------+----------------+---------------+\n\ +//! | C | Dennis Ritchie | 1972 |\n\ +//! +------+----------------+---------------+\n\ +//! | Rust | Graydon Hoare | 2010 |\n\ +//! +------+----------------+---------------+\n\ +//! | Go | Rob Pike | 2009 |\n\ +//! +------+----------------+---------------+"; +//! +//! assert_eq!(table, expected); +//! ``` +//! +//! Not all types can derive [`Tabled`] trait though. +//! The example below can't be compiled. +//! +//! ```rust,compile_fail +//! # use tabled::Tabled; +//! #[derive(Tabled)] +//! struct SomeType { +//! field1: SomeOtherType, +//! } +//! +//! struct SomeOtherType; +//! ``` +//! +//! Because `tabled` must know what we're up to print as a field, so +//! each (almoust) field must implement [`std::fmt::Display`]. +//! +//! ### Default implementations +//! +//! [`Table`] can be build from vast majority of Rust's standard types. +//! This allows you to run the following code. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Tabled, Table}; +//! let table = Table::new(&[1, 2, 3]); +//! # let expected = "+-----+\n\ +//! # | i32 |\n\ +//! # +-----+\n\ +//! # | 1 |\n\ +//! # +-----+\n\ +//! # | 2 |\n\ +//! # +-----+\n\ +//! # | 3 |\n\ +//! # +-----+"; +//! # assert_eq!(table.to_string(), expected); +//! ``` +//! +//! ### Dynamic table +//! +//! When you data scheme is not known at compile time. +//! You most likely will not able to relay on [`Tabled`] trait. +//! +//! So one option would be is to use [`Builder`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use std::iter; +//! +//! use tabled::{ +//! builder::Builder, +//! settings::{Modify, object::Rows, Alignment, Style} +//! }; +//! +//! let (x, y) = (3, 10); +//! +//! let mut builder = Builder::default(); +//! +//! let header = iter::once(String::from("i")) +//! .chain((0..y) +//! .map(|i| i.to_string())); +//! builder.set_header(header); +//! +//! for i in 0..x { +//! let row = iter::once(i) +//! .chain((0..y).map(|j| i * j)) +//! .map(|i| i.to_string()); +//! builder.push_record(row); +//! } +//! +//! let table = builder.build() +//! .with(Style::rounded()) +//! .with(Modify::new(Rows::new(1..)).with(Alignment::left())) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "╭───┬───┬───┬───┬───┬───┬────┬────┬────┬────┬────╮\n", +//! "│ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", +//! "├───┼───┼───┼───┼───┼───┼────┼────┼────┼────┼────┤\n", +//! "│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │\n", +//! "│ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", +//! "│ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │\n", +//! "╰───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────╯", +//! ) +//! ); +//! ``` +//! +//! ### Build table using [`row!`] and [`col!`] macros. +//! +#![cfg_attr(all(feature = "macros", feature = "std"), doc = "```")] +#![cfg_attr(not(all(feature = "macros", feature = "std")), doc = "```ignore")] +//! use tabled::{row, col}; +//! +//! let table = row![ +//! col!["Hello", "World", "!"], +//! col!["Hello"; 3], +//! col!["World"; 3], +//! ]; +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! "+-----------+-----------+-----------+\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "| | Hello | | | Hello | | | World | |\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "| | World | | | Hello | | | World | |\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "| | ! | | | Hello | | | World | |\n", +//! "| +-------+ | +-------+ | +-------+ |\n", +//! "+-----------+-----------+-----------+", +//! ) +//! ); +//! ``` +//! +//! ### Settings +//! +//! You can use many settings which is found in [`tabled::settings`] module. +//! +//! # Advanced +//! +//! ## Alloc +//! +//! [`Table`] keeps data buffered, which sometimes not ideal choise. +//! For such reason there is [`IterTable`] and [`CompactTable`]. +//! +//! ### Less allocations +//! +//! [`IterTable`] stands on a middle ground between [`Table`] and [`CompactTable`]. +//! +//! It does allocate memory but in a much smaller chunks that a [`Table`] does. +//! The benefit is that it can be used interchangebly with [`Table`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::tables::IterTable; +//! +//! let iterator = (0..3).map(|row| (0..4).map(move |col| format!("{}-{}", row, col))); +//! +//! let table = IterTable::new(iterator).to_string(); +//! +//! assert_eq!( +//! table, +//! "+-----+-----+-----+-----+\n\ +//! | 0-0 | 0-1 | 0-2 | 0-3 |\n\ +//! +-----+-----+-----+-----+\n\ +//! | 1-0 | 1-1 | 1-2 | 1-3 |\n\ +//! +-----+-----+-----+-----+\n\ +//! | 2-0 | 2-1 | 2-2 | 2-3 |\n\ +//! +-----+-----+-----+-----+", +//! ); +//! ``` +//! +//! ## Alloc free (`#nostd`) +//! +//! [`CompactTable`] can be configured ('1) to not make any allocations. +//! But the price is that the set of settings which can be applied to it is limited. +//! +//! It also can be printed directly to [`fmt::Write`] to not have any intermidiaries. +//! +//! '1. It does not make any allocations in case you provide it with `width` and `count_rows`. +//! +//! ``` +//! use tabled::{settings::Style, tables::CompactTable}; +//! use core::fmt::{Write, Result}; +//! +//! struct StubWriter; +//! +//! impl Write for StubWriter { +//! fn write_str(&mut self, _: &str) -> Result { +//! Ok(()) +//! } +//! } +//! +//! let data = [ +//! ["FreeBSD", "1993", "William and Lynne Jolitz", "?"], +//! ["OpenBSD", "1995", "Theo de Raadt", ""], +//! ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""], +//! ]; +//! +//! let table = CompactTable::from(data).with(Style::psql()); +//! +//! table.fmt(StubWriter); +//! ``` +//! +//! ## More information +//! +//! You can find more examples of settings and attributes in +//! [README.md](https://github.com/zhiburt/tabled/blob/master/README.md) +//! +//! [`Builder`]: crate::builder::Builder +//! [`IterTable`]: crate::tables::IterTable +//! [`CompactTable`]: crate::tables::CompactTable +//! [`fmt::Write`]: core::fmt::Write +//! [`row!`]: crate::row +//! [`col!`]: crate::col + +#![cfg_attr(not(any(feature = "std", test)), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/zhiburt/tabled/86ac146e532ce9f7626608d7fd05072123603a2e/assets/tabled-gear.svg" +)] +#![deny(unused_must_use)] +#![warn( + missing_docs, + rust_2018_idioms, + rust_2018_compatibility, + missing_debug_implementations, + unreachable_pub, + future_incompatible, + single_use_lifetimes, + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_import_braces, + unused_qualifications, + unused_results, + unused_variables, + variant_size_differences +)] +#![allow(clippy::uninlined_format_args)] + +#[cfg(feature = "std")] +mod tabled; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod builder; +pub mod settings; +pub mod tables; + +#[cfg(feature = "macros")] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +pub mod macros; + +pub mod grid; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use crate::{tabled::Tabled, tables::Table}; + +/// A derive to implement a [`Tabled`] trait. +/// +/// The macros available only when `derive` feature in turned on (and it is by default). +/// +/// To be able to use the derive each field must implement `std::fmt::Display`. +/// The following example will cause a error because of that. +/// +/// ```rust,compile_fail +/// use tabled::Tabled; +/// #[derive(Tabled)] +/// struct SomeType { +/// field1: SomeOtherType, +/// } +/// +/// struct SomeOtherType; +/// ``` +/// +/// Bellow you'll find available options for it. +/// +/// ### Override a column name +/// +/// You can use a `#[tabled(rename = "")]` attribute to override a column name. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// #[tabled(rename = "Name")] +/// first_name: &'static str, +/// #[tabled(rename = "Surname")] +/// last_name: &'static str, +/// } +/// ``` +/// +/// ### Hide a column +/// +/// You can mark fields as hidden in which case they fill be ignored and not be present on a sheet. +/// +/// A similar affect could be achieved by the means of a `Disable` setting. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// id: u8, +/// #[tabled(skip)] +/// number: &'static str, +/// name: &'static str, +/// } +/// ``` +/// +/// ### Set column order +/// +/// You can change the order in which they will be displayed in table. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// id: u8, +/// #[tabled(order = 0)] +/// number: &'static str, +/// #[tabled(order = 1)] +/// name: &'static str, +/// } +/// ``` +/// +/// ### Format fields +/// +/// As was said already, using `#[derive(Tabled)]` is possible only when all fields implement a `Display` trait. +/// However, this may be often not the case for example when a field uses the `Option` type. There's 2 common ways how to solve this: +/// +/// - Implement `Tabled` trait manually for a type. +/// - Wrap `Option` to something like `DisplayedOption<T>(Option<T>)` and implement a Display trait for it. +/// +/// Alternatively, you can use the `#[tabled(display_with = "func")]` attribute for the field to specify a display function. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// pub struct MyRecord { +/// pub id: i64, +/// #[tabled(display_with = "display_option")] +/// pub valid: Option<bool> +/// } +/// +/// fn display_option(o: &Option<bool>) -> String { +/// match o { +/// Some(s) => format!("is valid thing = {}", s), +/// None => format!("is not valid"), +/// } +/// } +/// ``` +/// +/// It's also possible to change function argument to be `&self`, +/// using `#[tabled(display_with("some_function", self))]` +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// pub struct MyRecord { +/// pub id: i64, +/// #[tabled(display_with("Self::display_valid", self))] +/// pub valid: Option<bool> +/// } +/// +/// impl MyRecord { +/// fn display_valid(&self) -> String { +/// match self.valid { +/// Some(s) => format!("is valid thing = {}", s), +/// None => format!("is not valid"), +/// } +/// } +/// } +/// ``` +/// +/// ### Format headers +/// +/// Beside `#[tabled(rename = "")]` you can change a format of a column name using +/// `#[tabled(rename_all = "UPPERCASE")]`. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// #[tabled(rename_all = "CamelCase")] +/// struct Person { +/// id: u8, +/// number: &'static str, +/// name: &'static str, +/// #[tabled(rename_all = "snake_case")] +/// middle_name: &'static str, +/// } +/// ``` +/// +/// ### Inline +/// +/// It's possible to inline internal data if it implements the `Tabled` trait using `#[tabled(inline)]`. +/// You can also set a prefix which will be used for all inlined elements by `#[tabled(inline("prefix>>"))]`. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// struct Person { +/// id: u8, +/// name: &'static str, +/// #[tabled(inline)] +/// ed: Education, +/// } +/// +/// #[derive(Tabled)] +/// struct Education { +/// uni: &'static str, +/// graduated: bool, +/// } +/// ``` +/// +/// And it works for enums as well. +/// +/// ```rust,no_run +/// use tabled::Tabled; +/// +/// #[derive(Tabled)] +/// enum Vehicle { +/// #[tabled(inline("Auto::"))] +/// Auto { +/// model: &'static str, +/// engine: &'static str, +/// }, +/// #[tabled(inline)] +/// Bikecycle( +/// &'static str, +/// #[tabled(inline)] Bike, +/// ), +/// } +/// +/// #[derive(Tabled)] +/// struct Bike { +/// brand: &'static str, +/// price: f32, +/// } +/// ``` +#[cfg(feature = "derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] +pub use tabled_derive::Tabled; diff --git a/vendor/tabled/src/macros/col.rs b/vendor/tabled/src/macros/col.rs new file mode 100644 index 000000000..1fd67de8a --- /dev/null +++ b/vendor/tabled/src/macros/col.rs @@ -0,0 +1,50 @@ +/// Creates a [`Table`] with [`Display`] arguments nested within. +/// +/// The macros allows several tables to be displayed vertically. +/// +/// Companion to [`row!`]. +/// +/// # Examples +/// ```rust,no_run +/// # use tabled::{row, col, Table}; +/// # let (table1, table2, table3) = (Table::new(&[String::new()]), Table::new(&[String::new()]), Table::new(&[String::new()])); +/// let new_table = col![table1, table2]; +/// let new_table_of_clones = col![table1; 3]; +/// let columns_and_rows = col![ +/// table1, +/// row![table2, table3] +/// ]; +/// ``` +/// +/// [`row!`]: crate::row +/// [`Table`]: crate::Table +/// [`Display`]: std::fmt::Display +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! col { + // Vertical + ( $($table:expr), * $(,)? ) => {{ + let mut builder = $crate::builder::Builder::default(); + + $( + builder.push_record([$table.to_string()]); + )* + + builder.build() + }}; + + // Duplicate single item + ( $table:expr; $N:expr) => {{ + let mut builder = $crate::builder::Builder::default(); + + let n = $N; + if n > 0 { + let t = $table.to_string(); + for _ in 0..$N { + builder.push_record([t.clone()]); + } + } + + builder.build() + }}; +} diff --git a/vendor/tabled/src/macros/mod.rs b/vendor/tabled/src/macros/mod.rs new file mode 100644 index 000000000..7db0a9caa --- /dev/null +++ b/vendor/tabled/src/macros/mod.rs @@ -0,0 +1,6 @@ +//! This module contains macro functions for dynamic [`Table`] construction. +//! +//! [`Table`]: crate::Table + +mod col; +mod row; diff --git a/vendor/tabled/src/macros/row.rs b/vendor/tabled/src/macros/row.rs new file mode 100644 index 000000000..23775b90d --- /dev/null +++ b/vendor/tabled/src/macros/row.rs @@ -0,0 +1,44 @@ +/// Creates a [`Table`] with [`Display`] arguments nested within. +/// +/// The macros allows several tables to be displayed horizontally. +/// +/// Companion to [`col!`]. +/// +/// # Examples +/// ```rust,no_run +/// # use tabled::{row, col, Table}; +/// # let (table1, table2, table3) = (Table::new(&[String::new()]), Table::new(&[String::new()]), Table::new(&[String::new()])); +/// let new_table = row![table1, table2]; +/// let new_table_of_clones = row![table1; 3]; +/// let rows_and_columns = row![ +/// table1, +/// col![table2, table3] +/// ]; +/// ``` +/// +/// [`col!`]: crate::col +/// [`Table`]: crate::Table +/// [`Display`]: std::fmt::Display +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +macro_rules! row { + // Horizontal Display + ( $($table:expr), * $(,)? ) => {{ + let mut builder = $crate::builder::Builder::default(); + + let record = [ $($table.to_string(),)* ]; + builder.push_record(record); + + builder.build() + }}; + + // Duplicate single item + ( $table:expr; $N:expr) => {{ + let mut builder = $crate::builder::Builder::default(); + + let duplicates = vec![$table.to_string(); $N]; + builder.push_record(duplicates); + + builder.build() + }}; +} diff --git a/vendor/tabled/src/settings/alignment/mod.rs b/vendor/tabled/src/settings/alignment/mod.rs new file mode 100644 index 000000000..81d1717a6 --- /dev/null +++ b/vendor/tabled/src/settings/alignment/mod.rs @@ -0,0 +1,189 @@ +//! This module contains an [`Alignment`] setting for cells on the [`Table`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! # use tabled::{Table, settings::{Alignment, Modify, object::Rows}}; +//! # let data: Vec<&'static str> = Vec::new(); +//! let mut table = Table::new(&data); +//! table.with(Modify::new(Rows::single(0)).with(Alignment::center())); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::config::CompactConfig, + grid::config::{AlignmentHorizontal, AlignmentVertical, CompactMultilineConfig}, + settings::TableOption, +}; + +use AlignmentInner::*; + +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, Entity}; + +/// Alignment represent a horizontal and vertical alignment setting for any cell on a [`Table`]. +/// +/// An alignment strategy can be set by [`AlignmentStrategy`]. +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{ +/// Table, +/// settings::{ +/// formatting::AlignmentStrategy, +/// object::Segment, Alignment, Modify, Style, +/// } +/// }; +/// +/// let data = [ +/// ["1", "2", "3"], +/// ["Some\nMulti\nLine\nText", "and a line", "here"], +/// ["4", "5", "6"], +/// ]; +/// +/// let mut table = Table::new(&data); +/// table +/// .with(Style::modern()) +/// .with( +/// Modify::new(Segment::all()) +/// .with(Alignment::right()) +/// .with(Alignment::center()) +/// .with(AlignmentStrategy::PerCell) +/// ); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "┌───────┬────────────┬──────┐\n", +/// "│ 0 │ 1 │ 2 │\n", +/// "├───────┼────────────┼──────┤\n", +/// "│ 1 │ 2 │ 3 │\n", +/// "├───────┼────────────┼──────┤\n", +/// "│ Some │ and a line │ here │\n", +/// "│ Multi │ │ │\n", +/// "│ Line │ │ │\n", +/// "│ Text │ │ │\n", +/// "├───────┼────────────┼──────┤\n", +/// "│ 4 │ 5 │ 6 │\n", +/// "└───────┴────────────┴──────┘", +/// ), +/// ) +/// ``` +/// +/// [`Table`]: crate::Table +/// [`AlignmentStrategy`]: crate::settings::formatting::AlignmentStrategy +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Alignment { + inner: AlignmentInner, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum AlignmentInner { + /// A horizontal alignment. + Horizontal(AlignmentHorizontal), + /// A vertical alignment. + Vertical(AlignmentVertical), +} + +impl Alignment { + /// Left constructs a horizontal alignment to [`AlignmentHorizontal::Left`] + pub fn left() -> Self { + Self::horizontal(AlignmentHorizontal::Left) + } + + /// Right constructs a horizontal alignment to [`AlignmentHorizontal::Right`] + /// + /// ## Notice + /// + /// When you use [`MinWidth`] the alignment might not work as you expected. + /// You could try to apply [`TrimStrategy`] which may help. + /// + /// [`MinWidth`]: crate::settings::width::MinWidth + /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy + pub fn right() -> Self { + Self::horizontal(AlignmentHorizontal::Right) + } + + /// Center constructs a horizontal alignment to [`AlignmentHorizontal::Center`] + /// + /// ## Notice + /// + /// When you use [`MinWidth`] the alignment might not work as you expected. + /// You could try to apply [`TrimStrategy`] which may help. + /// + /// [`MinWidth`]: crate::settings::width::MinWidth + /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy + pub const fn center() -> Self { + Self::horizontal(AlignmentHorizontal::Center) + } + + /// Top constructs a vertical alignment to [`AlignmentVertical::Top`] + pub const fn top() -> Self { + Self::vertical(AlignmentVertical::Top) + } + + /// Bottom constructs a vertical alignment to [`AlignmentVertical::Bottom`] + pub const fn bottom() -> Self { + Self::vertical(AlignmentVertical::Bottom) + } + + /// `Center_vertical` constructs a vertical alignment to [`AlignmentVertical::Center`] + pub const fn center_vertical() -> Self { + Self::vertical(AlignmentVertical::Center) + } + + /// Returns an alignment with the given horizontal alignment. + const fn horizontal(alignment: AlignmentHorizontal) -> Self { + Self::new(Horizontal(alignment)) + } + + /// Returns an alignment with the given vertical alignment. + const fn vertical(alignment: AlignmentVertical) -> Self { + Self::new(Vertical(alignment)) + } + + const fn new(inner: AlignmentInner) -> Self { + Self { inner } + } +} + +#[cfg(feature = "std")] +impl<R> crate::settings::CellOption<R, ColoredConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + match self.inner { + Horizontal(a) => cfg.set_alignment_horizontal(entity, a), + Vertical(a) => cfg.set_alignment_vertical(entity, a), + } + } +} + +#[cfg(feature = "std")] +impl<R, D> TableOption<R, D, ColoredConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + match self.inner { + Horizontal(a) => cfg.set_alignment_horizontal(Entity::Global, a), + Vertical(a) => cfg.set_alignment_vertical(Entity::Global, a), + } + } +} + +impl<R, D> TableOption<R, D, CompactConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + if let Horizontal(a) = self.inner { + *cfg = cfg.set_alignment_horizontal(a) + } + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for Alignment { + fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { + match self.inner { + Horizontal(a) => *cfg = cfg.set_alignment_horizontal(a), + Vertical(a) => *cfg = cfg.set_alignment_vertical(a), + } + } +} diff --git a/vendor/tabled/src/settings/cell_option.rs b/vendor/tabled/src/settings/cell_option.rs new file mode 100644 index 000000000..f589ac47f --- /dev/null +++ b/vendor/tabled/src/settings/cell_option.rs @@ -0,0 +1,55 @@ +use crate::grid::{ + config::Entity, + records::{ExactRecords, Records, RecordsMut}, +}; + +/// A trait for configuring a single cell. +/// +/// ~~~~ Where cell represented by 'row' and 'column' indexes. ~~~~ +/// +/// A cell can be targeted by [`Cell`]. +/// +/// [`Cell`]: crate::object::Cell +pub trait CellOption<R, C> { + /// Modification function of a single cell. + fn change(self, records: &mut R, cfg: &mut C, entity: Entity); +} + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, C> for String +where + R: Records + ExactRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { + (&self).change(records, cfg, entity); + } +} + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, C> for &String +where + R: Records + ExactRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + records.set(pos, self.clone()); + } + } +} + +impl<'a, R, C> CellOption<R, C> for &'a str +where + R: Records + ExactRecords + RecordsMut<&'a str>, +{ + fn change(self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + records.set(pos, self); + } + } +} diff --git a/vendor/tabled/src/settings/color/mod.rs b/vendor/tabled/src/settings/color/mod.rs new file mode 100644 index 000000000..f4bfa5c32 --- /dev/null +++ b/vendor/tabled/src/settings/color/mod.rs @@ -0,0 +1,346 @@ +//! This module contains a configuration of a [`Border`] or a [`Table`] to set its borders color via [`Color`]. +//! +//! [`Border`]: crate::settings::Border +//! [`Table`]: crate::Table + +use std::{borrow::Cow, ops::BitOr}; + +use crate::{ + grid::{ + color::{AnsiColor, StaticColor}, + config::{ColoredConfig, Entity}, + }, + settings::{CellOption, TableOption}, +}; + +/// Color represents a color which can be set to things like [`Border`], [`Padding`] and [`Margin`]. +/// +/// # Example +/// +/// ``` +/// use tabled::{settings::Color, Table}; +/// +/// let data = [ +/// (0u8, "Hello"), +/// (1u8, "World"), +/// ]; +/// +/// let table = Table::new(data) +/// .with(Color::BG_BLUE) +/// .to_string(); +/// +/// println!("{}", table); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +/// [`Margin`]: crate::settings::Margin +/// [`Border`]: crate::settings::Border +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Color(AnsiColor<'static>); + +// todo: Add | operation to combine colors + +#[rustfmt::skip] +impl Color { + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[30m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[34m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[90m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[94m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[96m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[92m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[95m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[91m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[97m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_BRIGHT_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[93m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[36m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[32m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[35m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[31m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[37m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const FG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[33m"), Cow::Borrowed("\u{1b}[39m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + + pub const BG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[40m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[44m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[100m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[104m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[106m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[102m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[105m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[101m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[107m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_BRIGHT_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[103m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[46m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[42m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[45m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[41m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[47m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[43m"), Cow::Borrowed("\u{1b}[49m"))); + /// A color representation. + /// + /// Notice that the colors are constants so you can't combine them. + pub const BOLD: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[1m"), Cow::Borrowed("\u{1b}[22m"))); +} + +impl Color { + /// Creates a new [`Color`]` instance, with ANSI prefix and ANSI suffix. + /// You can use [`TryFrom`] to construct it from [`String`]. + pub fn new(prefix: String, suffix: String) -> Self { + Self(AnsiColor::new(prefix.into(), suffix.into())) + } +} + +impl From<Color> for AnsiColor<'static> { + fn from(c: Color) -> Self { + c.0 + } +} + +impl From<AnsiColor<'static>> for Color { + fn from(c: AnsiColor<'static>) -> Self { + Self(c) + } +} + +impl From<StaticColor> for Color { + fn from(c: StaticColor) -> Self { + Self(AnsiColor::new( + Cow::Borrowed(c.get_prefix()), + Cow::Borrowed(c.get_suffix()), + )) + } +} + +impl BitOr for Color { + type Output = Color; + + fn bitor(self, rhs: Self) -> Self::Output { + let l_prefix = self.0.get_prefix(); + let l_suffix = self.0.get_suffix(); + let r_prefix = rhs.0.get_prefix(); + let r_suffix = rhs.0.get_suffix(); + + let mut prefix = l_prefix.to_string(); + if l_prefix != r_prefix { + prefix.push_str(r_prefix); + } + + let mut suffix = l_suffix.to_string(); + if l_suffix != r_suffix { + suffix.push_str(r_suffix); + } + + Self::new(prefix, suffix) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom<&str> for Color { + type Error = (); + + fn try_from(value: &str) -> Result<Self, Self::Error> { + AnsiColor::try_from(value).map(Color) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom<String> for Color { + type Error = (); + + fn try_from(value: String) -> Result<Self, Self::Error> { + AnsiColor::try_from(value).map(Color) + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let _ = cfg.set_color(Entity::Global, self.0.clone()); + } +} + +impl<R> CellOption<R, ColoredConfig> for Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let _ = cfg.set_color(entity, self.0.clone()); + } +} + +impl<R> CellOption<R, ColoredConfig> for &Color { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let _ = cfg.set_color(entity, self.0.clone()); + } +} + +impl crate::grid::color::Color for Color { + fn fmt_prefix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result { + self.0.fmt_prefix(f) + } + + fn fmt_suffix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result { + self.0.fmt_suffix(f) + } + + fn colorize<W: std::fmt::Write>(&self, f: &mut W, text: &str) -> std::fmt::Result { + self.0.colorize(f, text) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "color")] + use ::{owo_colors::OwoColorize, std::convert::TryFrom}; + + #[test] + fn test_xor_operation() { + assert_eq!( + Color::FG_BLACK | Color::FG_BLUE, + Color::new( + String::from("\u{1b}[30m\u{1b}[34m"), + String::from("\u{1b}[39m") + ) + ); + assert_eq!( + Color::FG_BRIGHT_GREEN | Color::BG_BLUE, + Color::new( + String::from("\u{1b}[92m\u{1b}[44m"), + String::from("\u{1b}[39m\u{1b}[49m") + ) + ); + assert_eq!( + Color::new(String::from("..."), String::from("!!!")) + | Color::new(String::from("@@@"), String::from("###")), + Color::new(String::from("...@@@"), String::from("!!!###")) + ); + assert_eq!( + Color::new(String::from("..."), String::from("!!!")) + | Color::new(String::from("@@@"), String::from("###")) + | Color::new(String::from("$$$"), String::from("%%%")), + Color::new(String::from("...@@@$$$"), String::from("!!!###%%%")) + ); + } + + #[cfg(feature = "color")] + #[test] + fn test_try_from() { + assert_eq!(Color::try_from(""), Err(())); + assert_eq!(Color::try_from("".red().on_green().to_string()), Err(())); + assert_eq!( + Color::try_from("."), + Ok(Color::new(String::new(), String::new())) + ); + assert_eq!( + Color::try_from("...."), + Ok(Color::new(String::new(), String::new())) + ); + assert_eq!( + Color::try_from(".".red().on_green().to_string()), + Ok(Color::new( + String::from("\u{1b}[31m\u{1b}[42m"), + String::from("\u{1b}[39m\u{1b}[49m") + )) + ); + assert_eq!( + Color::try_from("....".red().on_green().to_string()), + Ok(Color::new( + String::from("\u{1b}[31m\u{1b}[42m"), + String::from("\u{1b}[39m\u{1b}[49m") + )) + ); + } +} diff --git a/vendor/tabled/src/settings/concat/mod.rs b/vendor/tabled/src/settings/concat/mod.rs new file mode 100644 index 000000000..23b0dcda1 --- /dev/null +++ b/vendor/tabled/src/settings/concat/mod.rs @@ -0,0 +1,171 @@ +//! This module contains a [`Concat`] primitive which can be in order to combine 2 [`Table`]s into 1. +//! +//! # Example +//! +//! ``` +//! use tabled::{Table, settings::Concat}; +//! let table1 = Table::new([0, 1, 2, 3]); +//! let table2 = Table::new(["A", "B", "C", "D"]); +//! +//! let mut table3 = table1; +//! table3.with(Concat::horizontal(table2)); +//! ``` + +use std::borrow::Cow; + +use crate::{ + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable}, + settings::TableOption, + Table, +}; + +/// [`Concat`] concatenates tables along a particular axis [Horizontal | Vertical]. +/// It doesn't do any key or column comparisons like SQL's join does. +/// +/// When the tables has different sizes, empty cells will be created by default. +/// +/// [`Concat`] in horizontal mode has similar behaviour to tuples `(a, b)`. +/// But it behaves on tables rather than on an actual data. +/// +/// # Example +/// +/// +#[cfg_attr(feature = "derive", doc = "```")] +#[cfg_attr(not(feature = "derive"), doc = "```ignore")] +/// use tabled::{Table, Tabled, settings::{Style, Concat}}; +/// +/// #[derive(Tabled)] +/// struct Message { +/// id: &'static str, +/// text: &'static str, +/// } +/// +/// #[derive(Tabled)] +/// struct Department(#[tabled(rename = "department")] &'static str); +/// +/// let messages = [ +/// Message { id: "0", text: "Hello World" }, +/// Message { id: "1", text: "Do do do something", }, +/// ]; +/// +/// let departments = [ +/// Department("Admins"), +/// Department("DevOps"), +/// Department("R&D"), +/// ]; +/// +/// let mut table = Table::new(messages); +/// table +/// .with(Concat::horizontal(Table::new(departments))) +/// .with(Style::extended()); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "╔════╦════════════════════╦════════════╗\n", +/// "║ id ║ text ║ department ║\n", +/// "╠════╬════════════════════╬════════════╣\n", +/// "║ 0 ║ Hello World ║ Admins ║\n", +/// "╠════╬════════════════════╬════════════╣\n", +/// "║ 1 ║ Do do do something ║ DevOps ║\n", +/// "╠════╬════════════════════╬════════════╣\n", +/// "║ ║ ║ R&D ║\n", +/// "╚════╩════════════════════╩════════════╝", +/// ) +/// ) +/// ``` + +#[derive(Debug)] +pub struct Concat { + table: Table, + mode: ConcatMode, + default_cell: Cow<'static, str>, +} + +#[derive(Debug)] +enum ConcatMode { + Vertical, + Horizontal, +} + +impl Concat { + fn new(table: Table, mode: ConcatMode) -> Self { + Self { + table, + mode, + default_cell: Cow::Borrowed(""), + } + } + + /// Concatenate 2 tables horizontally (along axis=0) + pub fn vertical(table: Table) -> Self { + Self::new(table, ConcatMode::Vertical) + } + + /// Concatenate 2 tables vertically (along axis=1) + pub fn horizontal(table: Table) -> Self { + Self::new(table, ConcatMode::Horizontal) + } + + /// Sets a cell's content for cases where 2 tables has different sizes. + pub fn default_cell(mut self, cell: impl Into<Cow<'static, str>>) -> Self { + self.default_cell = cell.into(); + self + } +} + +impl<R, D, C> TableOption<R, D, C> for Concat +where + R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut<String>, +{ + fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let rhs = &mut self.table; + match self.mode { + ConcatMode::Horizontal => { + for _ in 0..rhs.count_columns() { + records.push_column(); + } + + for row in count_rows..rhs.count_rows() { + records.push_row(); + + for col in 0..records.count_columns() { + records.set((row, col), self.default_cell.to_string()); + } + } + + for row in 0..rhs.shape().0 { + for col in 0..rhs.shape().1 { + let text = rhs.get_records().get_text((row, col)).to_string(); + let col = col + count_cols; + records.set((row, col), text); + } + } + } + ConcatMode::Vertical => { + for _ in 0..rhs.count_rows() { + records.push_row(); + } + + for col in count_cols..rhs.shape().1 { + records.push_column(); + + for row in 0..records.count_rows() { + records.set((row, col), self.default_cell.to_string()); + } + } + + for row in 0..rhs.shape().0 { + for col in 0..rhs.shape().1 { + let text = rhs.get_records().get_text((row, col)).to_string(); + let row = row + count_rows; + records.set((row, col), text); + } + } + } + } + } +} diff --git a/vendor/tabled/src/settings/disable/mod.rs b/vendor/tabled/src/settings/disable/mod.rs new file mode 100644 index 000000000..bfbf03db0 --- /dev/null +++ b/vendor/tabled/src/settings/disable/mod.rs @@ -0,0 +1,196 @@ +//! This module contains a [`Disable`] structure which helps to +//! remove an etheir column or row from a [`Table`]. +//! +//! # Example +//! +//! ```rust,no_run +//! # use tabled::{Table, settings::{Disable, object::Rows}}; +//! # let data: Vec<&'static str> = Vec::new(); +//! let table = Table::new(&data).with(Disable::row(Rows::first())); +//! ``` +//! +//! [`Table`]: crate::Table + +use std::marker::PhantomData; + +use crate::{ + grid::records::{ExactRecords, Records, Resizable}, + settings::{locator::Locator, TableOption}, +}; + +/// Disable removes particular rows/columns from a [`Table`]. +/// +/// It tries to keeps track of style changes which may occur. +/// But it's not guaranteed will be the way you would expect it to be. +/// +/// Generally you should avoid use of [`Disable`] because it's a slow function and modifies the underlying records. +/// Providing correct data right away is better. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::{Disable, object::Rows}}; +/// +/// let data = vec!["Hello", "World", "!!!"]; +/// +/// let table = Table::new(data).with(Disable::row(Rows::new(1..2))).to_string(); +/// +/// assert_eq!( +/// table, +/// "+-------+\n\ +/// | &str |\n\ +/// +-------+\n\ +/// | World |\n\ +/// +-------+\n\ +/// | !!! |\n\ +/// +-------+" +/// ); +/// +/// ``` +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Disable<L, Target> { + locator: L, + target: PhantomData<Target>, +} + +impl<L> Disable<L, TargetColumn> { + /// Disable columns. + /// + /// Available locators are: + /// + /// - [`Columns`] + /// - [`Column`] + /// - [`FirstColumn`] + /// - [`LastColumn`] + /// - [`ByColumnName`] + /// + /// ```rust + /// use tabled::{builder::Builder, settings::{Disable, locator::ByColumnName, object::Columns}}; + /// + /// let mut builder = Builder::default(); + /// + /// builder.push_record(["col1", "col2", "col3"]); + /// builder.push_record(["Hello", "World", "1"]); + /// + /// let table = builder.build() + /// .with(Disable::column(ByColumnName::new("col3"))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+-------+\n\ + /// | col1 | col2 |\n\ + /// +-------+-------+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + /// + /// [`Columns`]: crate::settings::object::Columns + /// [`Column`]: crate::settings::object::Column + /// [`FirstColumn`]: crate::settings::object::FirstColumn + /// [`LastColumn`]: crate::settings::object::LastColumn + /// [`ByColumnName`]: crate::settings::locator::ByColumnName + pub fn column(locator: L) -> Self { + Self { + locator, + target: PhantomData, + } + } +} + +impl<L> Disable<L, TargetRow> { + /// Disable rows. + /// + /// Available locators are: + /// + /// - [`Rows`] + /// - [`Row`] + /// - [`FirstRow`] + /// - [`LastRow`] + /// + /// ```rust + /// use tabled::{settings::{Disable, object::Rows}, builder::Builder}; + /// + /// let mut builder = Builder::default(); + /// builder.push_record(["col1", "col2", "col3"]); + /// builder.push_record(["Hello", "World", "1"]); + /// + /// let table = builder.build() + /// .with(Disable::row(Rows::first())) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+-------+---+\n\ + /// | Hello | World | 1 |\n\ + /// +-------+-------+---+" + /// ); + /// ``` + /// + /// [`Rows`]: crate::settings::object::Rows + /// [`Row`]: crate::settings::object::Row + /// [`FirstRow`]: crate::settings::object::FirstRow + /// [`LastRow`]: crate::settings::object::LastRow + pub fn row(locator: L) -> Self { + Self { + locator, + target: PhantomData, + } + } +} + +/// A marker struct for [`Disable`]. +#[derive(Debug)] +pub struct TargetRow; + +/// A marker struct for [`Disable`]. +#[derive(Debug)] +pub struct TargetColumn; + +impl<L, R, D, C> TableOption<R, D, C> for Disable<L, TargetColumn> +where + for<'a> L: Locator<&'a R, Coordinate = usize>, + R: Records + Resizable, +{ + fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { + let columns = self.locator.locate(records).into_iter().collect::<Vec<_>>(); + + let mut shift = 0; + for col in columns.into_iter() { + if col - shift > records.count_columns() { + continue; + } + + records.remove_column(col - shift); + shift += 1; + } + + // fixme: I am pretty sure that we violate span constrains by removing rows/cols + // Because span may be bigger then the max number of rows/cols + } +} + +impl<L, R, D, C> TableOption<R, D, C> for Disable<L, TargetRow> +where + for<'a> L: Locator<&'a R, Coordinate = usize>, + R: ExactRecords + Resizable, +{ + fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { + let rows = self.locator.locate(records).into_iter().collect::<Vec<_>>(); + + let mut shift = 0; + for row in rows.into_iter() { + if row - shift > records.count_rows() { + continue; + } + + records.remove_row(row - shift); + shift += 1; + } + + // fixme: I am pretty sure that we violate span constrains by removing rows/cols + // Because span may be bigger then the max number of rows/cols + } +} diff --git a/vendor/tabled/src/settings/duplicate/mod.rs b/vendor/tabled/src/settings/duplicate/mod.rs new file mode 100644 index 000000000..dec967bd6 --- /dev/null +++ b/vendor/tabled/src/settings/duplicate/mod.rs @@ -0,0 +1,150 @@ +//! This module contains an [`Dup`] setting the [`Table`]. +//! +//! # Example +//! +//! ``` +//! # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}}; +//! # let data: Vec<&'static str> = Vec::new(); +//! let mut table = Table::new(&data); +//! table.with(Dup::new(Rows::first(), Columns::first())); +//! ``` +//! +//! [`Table`]: crate::Table + +use papergrid::config::Position; + +use crate::{ + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{object::Object, TableOption}, +}; + +/// [`Dup`] duplicates a given set of cells into another set of ones [`Table`]. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::{object::Rows, Dup}}; +/// +/// let data = [ +/// ["1", "2", "3"], +/// ["Some\nMulti\nLine\nText", "and a line", "here"], +/// ["4", "5", "6"], +/// ]; +/// +/// let mut table = Table::new(&data); +/// table.with(Dup::new(Rows::single(1), Rows::single(2))); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+------------+------+\n\ +/// | 0 | 1 | 2 |\n\ +/// +-------+------------+------+\n\ +/// | Some | and a line | here |\n\ +/// | Multi | | |\n\ +/// | Line | | |\n\ +/// | Text | | |\n\ +/// +-------+------------+------+\n\ +/// | Some | and a line | here |\n\ +/// | Multi | | |\n\ +/// | Line | | |\n\ +/// | Text | | |\n\ +/// +-------+------------+------+\n\ +/// | 4 | 5 | 6 |\n\ +/// +-------+------------+------+", +/// ) +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Dup<Dst, Src> { + src: Src, + dst: Dst, +} + +impl<Dst, Src> Dup<Dst, Src> { + /// New creates a new [`Dup`] modifier. + /// + /// # Example + /// + /// ``` + /// # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}}; + /// # let data: Vec<&'static str> = Vec::new(); + /// let mut table = Table::new(&data); + /// table.with(Dup::new(Rows::first(), Columns::last())); + /// ``` + pub fn new(dst: Dst, src: Src) -> Self { + Self { src, dst } + } +} + +impl<Dst, Src, R, D, C> TableOption<R, D, C> for Dup<Dst, Src> +where + Dst: Object<R>, + Src: Object<R>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let input = collect_input(records, self.src); + set_cells(records, &input, self.dst); + } +} + +fn collect_input<R, O>(records: &mut R, src: O) -> Vec<String> +where + O: Object<R>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let mut input = Vec::new(); + for entity in src.cells(records) { + for pos in entity.iter(count_rows, count_columns) { + if !is_valid_cell(pos, count_rows, count_columns) { + continue; + } + + let text = records.get_text(pos).to_owned(); + input.push(text); + } + } + + input +} + +fn set_cells<R, O>(records: &mut R, src: &[String], dst: O) +where + O: Object<R>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + if src.is_empty() { + return; + } + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for entity in dst.cells(records) { + let mut source = src.iter().cycle(); + for pos in entity.iter(count_rows, count_columns) { + if !is_valid_cell(pos, count_rows, count_columns) { + continue; + } + + let text = source.next().unwrap().clone(); + records.set(pos, text); + } + } +} + +fn is_valid_cell((row, col): Position, count_rows: usize, count_columns: usize) -> bool { + if row > count_rows { + return false; + } + + if col > count_columns { + return false; + } + + true +} diff --git a/vendor/tabled/src/settings/extract/mod.rs b/vendor/tabled/src/settings/extract/mod.rs new file mode 100644 index 000000000..bba90a0db --- /dev/null +++ b/vendor/tabled/src/settings/extract/mod.rs @@ -0,0 +1,257 @@ +//! This module contains an [`Extract`] structure which is used to +//! obtain an ordinary segment from the [`Table`]. +//! +//! There's a similar structure [`Highlight`] which does a highlighting a of segments. +//! +//! [`Table`]: crate::Table +//! [`Highlight`]: crate::settings::highlight::Highlight + +use core::cmp::min; +use core::ops::{Bound, RangeBounds, RangeFull}; + +use crate::{ + grid::records::{ExactRecords, Records, Resizable}, + settings::TableOption, +}; + +/// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{Table, settings::{Format, object::Rows, Modify, Extract}}; +/// +/// let data = vec![ +/// (0, "Grodno", true), +/// (1, "Minsk", true), +/// (2, "Hamburg", false), +/// (3, "Brest", true), +/// ]; +/// +/// let table = Table::new(&data) +/// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s)))) +/// .with(Extract::segment(1..=2, 1..)) +/// .to_string(); +/// +/// assert_eq!(table, "+------------+----------+\n\ +/// | : Grodno : | : true : |\n\ +/// +------------+----------+\n\ +/// | : Minsk : | : true : |\n\ +/// +------------+----------+"); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Extract<R, C> { + rows: R, + columns: C, +} + +impl<R, C> Extract<R, C> +where + R: RangeBounds<usize>, + C: RangeBounds<usize>, +{ + /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] + /// + /// ```rust,no_run + /// # use tabled::settings::Extract; + /// let rows = 1..3; + /// let columns = 1..; + /// Extract::segment(rows, columns); + /// ``` + /// + /// # Range + /// + /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] + /// + /// If a [`RangeBounds`] argument is malformed or too large the thread will panic + /// + /// ```text + /// // Empty Full Out of bounds + /// Extract::segment(0..0, 0..0) Extract::segment(.., ..) Extract::segment(0..1, ..4) + /// []. . . [O O O [O O O X] //ERROR + /// . . . O O O . . . + /// . . . O O O] . . . + /// ``` + /// + /// [`Table`]: crate::Table + pub fn segment(rows: R, columns: C) -> Self { + Extract { rows, columns } + } +} + +impl<R> Extract<R, RangeFull> +where + R: RangeBounds<usize>, +{ + /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] + /// + /// The segment is defined by [`RangeBounds`] for Rows + /// + /// ```rust,no_run + /// # use tabled::settings::Extract; + /// Extract::rows(1..3); + /// ``` + /// + /// # Range + /// + /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] + /// + /// If a [`RangeBounds`] argument is malformed or too large the thread will panic + /// + /// ```text + /// // Empty Full Out of bounds + /// Extract::rows(0..0) Extract::rows(..) Extract::rows(0..4) + /// []. . . [O O O [O O O + /// . . . O O O O O O + /// . . . O O O] O O O + /// X X X] // ERROR + /// ``` + /// + /// [`Table`]: crate::Table + pub fn rows(rows: R) -> Self { + Extract { rows, columns: .. } + } +} + +impl<C> Extract<RangeFull, C> +where + C: RangeBounds<usize>, +{ + /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] + /// + /// The segment is defined by [`RangeBounds`] for columns. + /// + /// ```rust,no_run + /// # use tabled::settings::Extract; + /// Extract::columns(1..3); + /// ``` + /// + /// # Range + /// + /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] + /// + /// If a [`RangeBounds`] argument is malformed or too large the thread will panic + /// + /// ```text + /// // Empty Full Out of bounds + /// Extract::columns(0..0) Extract::columns(..) Extract::columns(0..4) + /// []. . . [O O O [O O O X + /// . . . O O O O O O X + /// . . . O O O] O O O X] // ERROR + /// ``` + /// + /// [`Table`]: crate::Table + pub fn columns(columns: C) -> Self { + Extract { rows: .., columns } + } +} + +impl<R, C, RR, D, Cfg> TableOption<RR, D, Cfg> for Extract<R, C> +where + R: RangeBounds<usize> + Clone, + C: RangeBounds<usize> + Clone, + RR: Records + ExactRecords + Resizable, +{ + fn change(self, records: &mut RR, _: &mut Cfg, _: &mut D) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let mut rows = bounds_to_usize(self.rows.start_bound(), self.rows.end_bound(), count_rows); + let mut cols = bounds_to_usize( + self.columns.start_bound(), + self.columns.end_bound(), + count_columns, + ); + + // Cleanup table in case if boundaries are exceeded. + // + // todo: can be optimized by adding a clear() method to Resizable + rows.0 = min(rows.0, count_rows); + cols.0 = min(cols.0, count_columns); + + extract(records, (count_rows, count_columns), rows, cols); + } +} + +/// Returns a new [`Grid`] that reflects a segment of the referenced [`Grid`]. +/// +/// # Example +/// +/// ```text +/// grid +/// +---+---+---+ +/// |0-0|0-1|0-2| +/// +---+---+---+ +/// |1-0|1-1|1-2| +/// +---+---+---+ +/// |2-0|2-1|2-2| +/// +---+---+---+ +/// +/// let rows = ..; +/// let columns = ..1; +/// grid.extract(rows, columns) +/// +/// grid +/// +---+ +/// |0-0| +/// +---+ +/// |1-0| +/// +---+ +/// |2-0| +/// +---+ +/// ``` +fn extract<R>( + records: &mut R, + (count_rows, count_cols): (usize, usize), + (start_row, end_row): (usize, usize), + (start_col, end_col): (usize, usize), +) where + R: Resizable, +{ + for (i, row) in (0..start_row).enumerate() { + let row = row - i; + records.remove_row(row); + } + + let count_rows = count_rows - start_row; + let end_row = end_row - start_row; + for (i, row) in (end_row..count_rows).enumerate() { + let row = row - i; + records.remove_row(row); + } + + for (i, col) in (0..start_col).enumerate() { + let col = col - i; + records.remove_column(col); + } + + let count_cols = count_cols - start_col; + let end_col = end_col - start_col; + for (i, col) in (end_col..count_cols).enumerate() { + let col = col - i; + records.remove_column(col); + } +} + +fn bounds_to_usize( + left: Bound<&usize>, + right: Bound<&usize>, + count_elements: usize, +) -> (usize, usize) { + match (left, right) { + (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), + (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), + (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), + (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), + (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), + (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), + (Bound::Excluded(_), Bound::Unbounded) + | (Bound::Excluded(_), Bound::Included(_)) + | (Bound::Excluded(_), Bound::Excluded(_)) => { + unreachable!("A start bound can't be excluded") + } + } +} diff --git a/vendor/tabled/src/settings/format/format_config.rs b/vendor/tabled/src/settings/format/format_config.rs new file mode 100644 index 000000000..247ead344 --- /dev/null +++ b/vendor/tabled/src/settings/format/format_config.rs @@ -0,0 +1,14 @@ +use crate::settings::TableOption; + +/// This is a struct wrapper for a lambda which changes config. +#[derive(Debug)] +pub struct FormatConfig<F>(pub(crate) F); + +impl<F, R, D, C> TableOption<R, D, C> for FormatConfig<F> +where + F: FnMut(&mut C), +{ + fn change(mut self, _: &mut R, cfg: &mut C, _: &mut D) { + (self.0)(cfg); + } +} diff --git a/vendor/tabled/src/settings/format/format_content.rs b/vendor/tabled/src/settings/format/format_content.rs new file mode 100644 index 000000000..c14eee64f --- /dev/null +++ b/vendor/tabled/src/settings/format/format_content.rs @@ -0,0 +1,86 @@ +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{CellOption, TableOption}, +}; + +/// A lambda which formats cell content. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct FormatContent<F> { + f: F, + multiline: bool, +} + +impl<F> FormatContent<F> { + pub(crate) fn new(f: F) -> Self { + Self { + f, + multiline: false, + } + } +} + +impl<F> FormatContent<F> { + /// Multiline a helper function for changing multiline content of cell. + /// Using this formatting applied for all rows not to a string as a whole. + /// + /// ```rust,no_run + /// use tabled::{Table, settings::{Format, object::Segment, Modify}}; + /// + /// let data: Vec<&'static str> = Vec::new(); + /// let table = Table::new(&data) + /// .with(Modify::new(Segment::all()).with(Format::content(|s| s.to_string()).multiline())) + /// .to_string(); + /// ``` + pub fn multiline(mut self) -> Self { + self.multiline = true; + self + } +} + +impl<F, R, D, C> TableOption<R, D, C> for FormatContent<F> +where + F: FnMut(&str) -> String + Clone, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + CellOption::change(self, records, cfg, Entity::Global); + } +} + +impl<F, R, C> CellOption<R, C> for FormatContent<F> +where + F: FnMut(&str) -> String + Clone, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(mut self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols; + if !is_valid_pos { + continue; + } + + let content = records.get_text(pos); + let content = if self.multiline { + multiline(self.f.clone())(content) + } else { + (self.f)(content) + }; + records.set(pos, content); + } + } +} + +fn multiline<F: FnMut(&str) -> String>(mut f: F) -> impl FnMut(&str) -> String { + move |s: &str| { + let mut v = Vec::new(); + for line in s.lines() { + v.push(f(line)); + } + + v.join("\n") + } +} diff --git a/vendor/tabled/src/settings/format/format_positioned.rs b/vendor/tabled/src/settings/format/format_positioned.rs new file mode 100644 index 000000000..e121f007e --- /dev/null +++ b/vendor/tabled/src/settings/format/format_positioned.rs @@ -0,0 +1,51 @@ +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{CellOption, TableOption}, +}; + +/// [`FormatContentPositioned`] is like a [`FormatContent`] an abstraction over a function you can use against a cell. +/// +/// It different from [`FormatContent`] that it provides a row and column index. +/// +/// [`FormatContent`]: crate::settings::format::FormatContent +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct FormatContentPositioned<F>(F); + +impl<F> FormatContentPositioned<F> { + pub(crate) fn new(f: F) -> Self { + Self(f) + } +} + +impl<F, R, D, C> TableOption<R, D, C> for FormatContentPositioned<F> +where + F: FnMut(&str, (usize, usize)) -> String, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + CellOption::change(self, records, cfg, Entity::Global); + } +} + +impl<F, R, C> CellOption<R, C> for FormatContentPositioned<F> +where + F: FnMut(&str, (usize, usize)) -> String, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(mut self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + for pos in entity.iter(count_rows, count_cols) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols; + if !is_valid_pos { + continue; + } + + let content = records.get_text(pos); + let content = (self.0)(content, pos); + records.set(pos, content); + } + } +} diff --git a/vendor/tabled/src/settings/format/mod.rs b/vendor/tabled/src/settings/format/mod.rs new file mode 100644 index 000000000..e8221ae32 --- /dev/null +++ b/vendor/tabled/src/settings/format/mod.rs @@ -0,0 +1,144 @@ +//! This module contains a list of primitives to help to modify a [`Table`]. +//! +//! [`Table`]: crate::Table + +mod format_config; +mod format_content; +mod format_positioned; + +pub use format_config::FormatConfig; +pub use format_content::FormatContent; +pub use format_positioned::FormatContentPositioned; + +/// A formatting function of particular cells on a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Format; + +impl Format { + /// This function creates a new [`Format`] instance, so + /// it can be used as a grid setting. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Format, object::Rows, Modify}}; + /// + /// let data = vec![ + /// (0, "Grodno", true), + /// (1, "Minsk", true), + /// (2, "Hamburg", false), + /// (3, "Brest", true), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s)))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-------+-------------+-----------+\n\ + /// | i32 | &str | bool |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 0 : | : Grodno : | : true : |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 1 : | : Minsk : | : true : |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 2 : | : Hamburg : | : false : |\n\ + /// +-------+-------------+-----------+\n\ + /// | : 3 : | : Brest : | : true : |\n\ + /// +-------+-------------+-----------+" + /// ); + /// ``` + pub fn content<F>(f: F) -> FormatContent<F> + where + F: FnMut(&str) -> String, + { + FormatContent::new(f) + } + + /// This function creates a new [`FormatContentPositioned`], so + /// it can be used as a grid setting. + /// + /// It's different from [`Format::content`] as it also provides a row and column index. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Format, object::Rows, Modify}}; + /// + /// let data = vec![ + /// (0, "Grodno", true), + /// (1, "Minsk", true), + /// (2, "Hamburg", false), + /// (3, "Brest", true), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Modify::new(Rows::single(0)).with(Format::positioned(|_, (_, col)| col.to_string()))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+---+---------+-------+\n\ + /// | 0 | 1 | 2 |\n\ + /// +---+---------+-------+\n\ + /// | 0 | Grodno | true |\n\ + /// +---+---------+-------+\n\ + /// | 1 | Minsk | true |\n\ + /// +---+---------+-------+\n\ + /// | 2 | Hamburg | false |\n\ + /// +---+---------+-------+\n\ + /// | 3 | Brest | true |\n\ + /// +---+---------+-------+" + /// ); + /// ``` + pub fn positioned<F>(f: F) -> FormatContentPositioned<F> + where + F: FnMut(&str, (usize, usize)) -> String, + { + FormatContentPositioned::new(f) + } + + /// This function creates [`FormatConfig`] function to modify a table config. + /// + /// # Example + /// + /// ``` + /// use tabled::{ + /// Table, + /// settings::{Format, object::Rows, Modify}, + /// grid::config::ColoredConfig, + /// }; + /// + /// let data = vec![ + /// (0, "Grodno", true), + /// (1, "Minsk", true), + /// (2, "Hamburg", false), + /// (3, "Brest", true), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Format::config(|cfg: &mut ColoredConfig| cfg.set_justification((0,1).into(), '.'))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+-----+---------+-------+\n\ + /// | i32 | &str... | bool |\n\ + /// +-----+---------+-------+\n\ + /// | 0 | Grodno | true |\n\ + /// +-----+---------+-------+\n\ + /// | 1 | Minsk | true |\n\ + /// +-----+---------+-------+\n\ + /// | 2 | Hamburg | false |\n\ + /// +-----+---------+-------+\n\ + /// | 3 | Brest | true |\n\ + /// +-----+---------+-------+" + /// ); + /// ``` + pub fn config<F>(f: F) -> FormatConfig<F> { + FormatConfig(f) + } +} diff --git a/vendor/tabled/src/settings/formatting/alignment_strategy.rs b/vendor/tabled/src/settings/formatting/alignment_strategy.rs new file mode 100644 index 000000000..c95facc5c --- /dev/null +++ b/vendor/tabled/src/settings/formatting/alignment_strategy.rs @@ -0,0 +1,172 @@ +use crate::{ + grid::config::{ColoredConfig, CompactMultilineConfig, Entity}, + settings::{CellOption, TableOption}, +}; + +/// `AlignmentStrategy` is a responsible for a flow how we apply an alignment. +/// It mostly matters for multiline strings. +/// +/// # Examples +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Style, Modify, Alignment, object::Segment, +/// formatting::{AlignmentStrategy, TrimStrategy} +/// } +/// }; +/// +/// // sample_from: https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html +/// let json = r#" +/// { +/// "id": "0001", +/// "type": "donut", +/// "name": "Cake", +/// "ppu": 0.55, +/// "batters": { +/// "batter": [ +/// { "id": "1001", "type": "Regular" }, +/// { "id": "1002", "type": "Chocolate" }, +/// ] +/// }, +/// "topping": [ +/// { "id": "5001", "type": "None" }, +/// { "id": "5006", "type": "Chocolate with Sprinkles" }, +/// { "id": "5003", "type": "Chocolate" }, +/// { "id": "5004", "type": "Maple" } +/// ] +/// }"#; +/// +/// let mut table = Table::new(&[json]); +/// table +/// .with(Style::modern()) +/// .with(Modify::new(Segment::all()).with(Alignment::right())) +/// .with(Modify::new(Segment::all()).with(TrimStrategy::None)); +/// +/// println!("{}", table); +/// +/// assert_eq!( +/// format!("\n{}", table), +/// r#" +/// ┌───────────────────────────────────────────────────────────────┐ +/// │ &str │ +/// ├───────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ { │ +/// │ "id": "0001", │ +/// │ "type": "donut", │ +/// │ "name": "Cake", │ +/// │ "ppu": 0.55, │ +/// │ "batters": { │ +/// │ "batter": [ │ +/// │ { "id": "1001", "type": "Regular" }, │ +/// │ { "id": "1002", "type": "Chocolate" }, │ +/// │ ] │ +/// │ }, │ +/// │ "topping": [ │ +/// │ { "id": "5001", "type": "None" }, │ +/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ +/// │ { "id": "5003", "type": "Chocolate" }, │ +/// │ { "id": "5004", "type": "Maple" } │ +/// │ ] │ +/// │ } │ +/// └───────────────────────────────────────────────────────────────┘"#); +/// +/// table +/// .with(Modify::new(Segment::all()).with(AlignmentStrategy::PerCell)) +/// .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)); +/// +/// assert_eq!( +/// format!("\n{}", table), +/// r#" +/// ┌───────────────────────────────────────────────────────────────┐ +/// │ &str │ +/// ├───────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ { │ +/// │ "id": "0001", │ +/// │ "type": "donut", │ +/// │ "name": "Cake", │ +/// │ "ppu": 0.55, │ +/// │ "batters": { │ +/// │ "batter": [ │ +/// │ { "id": "1001", "type": "Regular" }, │ +/// │ { "id": "1002", "type": "Chocolate" }, │ +/// │ ] │ +/// │ }, │ +/// │ "topping": [ │ +/// │ { "id": "5001", "type": "None" }, │ +/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ +/// │ { "id": "5003", "type": "Chocolate" }, │ +/// │ { "id": "5004", "type": "Maple" } │ +/// │ ] │ +/// │ } │ +/// └───────────────────────────────────────────────────────────────┘"#); +/// +/// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); +/// +/// assert_eq!( +/// format!("\n{}", table), +/// r#" +/// ┌───────────────────────────────────────────────────────────────┐ +/// │ &str │ +/// ├───────────────────────────────────────────────────────────────┤ +/// │ │ +/// │ { │ +/// │ "id": "0001", │ +/// │ "type": "donut", │ +/// │ "name": "Cake", │ +/// │ "ppu": 0.55, │ +/// │ "batters": { │ +/// │ "batter": [ │ +/// │ { "id": "1001", "type": "Regular" }, │ +/// │ { "id": "1002", "type": "Chocolate" }, │ +/// │ ] │ +/// │ }, │ +/// │ "topping": [ │ +/// │ { "id": "5001", "type": "None" }, │ +/// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ +/// │ { "id": "5003", "type": "Chocolate" }, │ +/// │ { "id": "5004", "type": "Maple" } │ +/// │ ] │ +/// │ } │ +/// └───────────────────────────────────────────────────────────────┘"#); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AlignmentStrategy { + /// Apply alignment for cell content as a whole. + PerCell, + /// Apply alignment for each line of a cell content as a whole. + PerLine, +} + +impl<R> CellOption<R, ColoredConfig> for AlignmentStrategy { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let mut formatting = *cfg.get_formatting(entity); + match &self { + AlignmentStrategy::PerCell => formatting.allow_lines_alignment = false, + AlignmentStrategy::PerLine => formatting.allow_lines_alignment = true, + } + + cfg.set_formatting(entity, formatting); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for AlignmentStrategy { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + <Self as CellOption<R, ColoredConfig>>::change(self, records, cfg, Entity::Global) + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for AlignmentStrategy { + fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { + let mut f = cfg.get_formatting(); + match &self { + AlignmentStrategy::PerCell => f.allow_lines_alignment = false, + AlignmentStrategy::PerLine => f.allow_lines_alignment = true, + } + + *cfg = cfg.set_formatting(f); + } +} diff --git a/vendor/tabled/src/settings/formatting/charset.rs b/vendor/tabled/src/settings/formatting/charset.rs new file mode 100644 index 000000000..c58effcd8 --- /dev/null +++ b/vendor/tabled/src/settings/formatting/charset.rs @@ -0,0 +1,108 @@ +use papergrid::{ + config::Entity, + records::{ExactRecords, PeekableRecords}, +}; + +use crate::{ + grid::records::{Records, RecordsMut}, + settings::{CellOption, TableOption}, +}; + +/// A structure to handle special chars. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Charset; + +impl Charset { + /// Returns [`CleanCharset`] which removes all `\t` and `\r` occurences. + /// + /// Notice that tab is just removed rather then being replaced with spaces. + /// You might be better call [`TabSize`] first if you not expect such behavior. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::formatting::Charset}; + /// + /// let text = "Some\ttext\t\twith \\tabs"; + /// + /// let mut table = Table::new([text]); + /// table.with(Charset::clean()); + /// + /// assert_eq!( + /// table.to_string(), + /// "+--------------------+\n\ + /// | &str |\n\ + /// +--------------------+\n\ + /// | Sometextwith \\tabs |\n\ + /// +--------------------+" + /// ) + /// ``` + /// + /// [`TabSize`]: crate::settings::formatting::TabSize + pub fn clean() -> CleanCharset { + CleanCharset + } +} + +/// [`CleanCharset`] removes all `\t` and `\r` occurences. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::formatting::Charset}; +/// +/// let text = "Some text which was created on windows \r\n yes they use this \\r\\n"; +/// +/// let mut builder = Table::builder([text]); +/// builder.set_header(["win. text"]); +/// +/// let mut table = builder.build(); +/// table.with(Charset::clean()); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-----------------------------------------+\n\ +/// | win. text |\n\ +/// +-----------------------------------------+\n\ +/// | Some text which was created on windows |\n\ +/// | yes they use this \\r\\n |\n\ +/// +-----------------------------------------+" +/// ) +/// ``` +#[derive(Debug, Default, Clone)] +pub struct CleanCharset; + +impl<R, D, C> TableOption<R, D, C> for CleanCharset +where + for<'a> &'a R: Records, + R: RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let mut list = vec![]; + for (row, cells) in records.iter_rows().into_iter().enumerate() { + for (col, text) in cells.into_iter().enumerate() { + let text = text.as_ref().replace(['\t', '\r'], ""); + list.push(((row, col), text)); + } + } + + for (pos, text) in list { + records.set(pos, text); + } + } +} + +impl<R, C> CellOption<R, C> for CleanCharset +where + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + for pos in entity.iter(count_rows, count_cols) { + let text = records.get_text(pos); + let text = text.replace(['\t', '\r'], ""); + records.set(pos, text); + } + } +} diff --git a/vendor/tabled/src/settings/formatting/justification.rs b/vendor/tabled/src/settings/formatting/justification.rs new file mode 100644 index 000000000..a427eb95e --- /dev/null +++ b/vendor/tabled/src/settings/formatting/justification.rs @@ -0,0 +1,127 @@ +use crate::{ + grid::{ + color::AnsiColor, + config::{ColoredConfig, Entity}, + }, + settings::{CellOption, Color, TableOption}, +}; + +/// Set a justification character and a color. +/// +/// Default value is `' '` (`<space>`) with no color. +/// +/// # Examples +/// +/// Setting a justification character. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::formatting::Justification, +/// }; +/// +/// let mut table = Table::new(&[("Hello", ""), ("", "World")]); +/// table.with(Justification::new('#')); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+-------+\n\ +/// | &str# | &str# |\n\ +/// +-------+-------+\n\ +/// | Hello | ##### |\n\ +/// +-------+-------+\n\ +/// | ##### | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +/// +/// Setting a justification color. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{formatting::Justification, Color}, +/// }; +/// +/// let mut table = Table::new(&[("Hello", ""), ("", "World")]); +/// table.with(Justification::default().color(Color::BG_BRIGHT_RED)); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+-------+\n\ +/// | &str\u{1b}[101m \u{1b}[49m | &str\u{1b}[101m \u{1b}[49m |\n\ +/// +-------+-------+\n\ +/// | Hello | \u{1b}[101m \u{1b}[49m |\n\ +/// +-------+-------+\n\ +/// | \u{1b}[101m \u{1b}[49m | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +/// +/// Use different justification for different columns. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{Modify, object::Columns, formatting::Justification}, +/// }; +/// +/// let mut table = Table::new(&[("Hello", ""), ("", "World")]); +/// table.with(Modify::new(Columns::single(0)).with(Justification::new('#'))); +/// table.with(Modify::new(Columns::single(1)).with(Justification::new('@'))); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-------+-------+\n\ +/// | &str# | &str@ |\n\ +/// +-------+-------+\n\ +/// | Hello | @@@@@ |\n\ +/// +-------+-------+\n\ +/// | ##### | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +/// +#[derive(Debug, Default, Clone)] +pub struct Justification { + c: Option<char>, + color: Option<AnsiColor<'static>>, +} + +impl Justification { + /// Creates new [`Justification`] object. + pub fn new(c: char) -> Self { + Self { + c: Some(c), + color: None, + } + } + + /// Sets a color for a justification. + pub fn color(self, color: Color) -> Self { + Self { + c: self.c, + color: Some(color.into()), + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Justification { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let c = self.c.unwrap_or(' '); + let color = self.color; + + cfg.set_justification(Entity::Global, c); + cfg.set_justification_color(Entity::Global, color); + } +} + +impl<R> CellOption<R, ColoredConfig> for Justification { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let c = self.c.unwrap_or(' '); + let color = self.color; + + cfg.set_justification(entity, c); + cfg.set_justification_color(entity, color); + } +} diff --git a/vendor/tabled/src/settings/formatting/mod.rs b/vendor/tabled/src/settings/formatting/mod.rs new file mode 100644 index 000000000..e3a56092f --- /dev/null +++ b/vendor/tabled/src/settings/formatting/mod.rs @@ -0,0 +1,20 @@ +//! This module contains settings for render strategy of papergrid. +//! +//! - [`TrimStrategy`] and [`AlignmentStrategy`] allows to set [`Alignment`] settings. +//! - [`TabSize`] sets a default tab size. +//! - [`Charset`] responsible for special char treatment. +//! - [`Justification`] responsible for justification space of content. +//! +//! [`Alignment`]: crate::settings::Alignment + +mod alignment_strategy; +mod charset; +mod justification; +mod tab_size; +mod trim_strategy; + +pub use alignment_strategy::AlignmentStrategy; +pub use charset::{Charset, CleanCharset}; +pub use justification::Justification; +pub use tab_size::TabSize; +pub use trim_strategy::TrimStrategy; diff --git a/vendor/tabled/src/settings/formatting/tab_size.rs b/vendor/tabled/src/settings/formatting/tab_size.rs new file mode 100644 index 000000000..677d0d613 --- /dev/null +++ b/vendor/tabled/src/settings/formatting/tab_size.rs @@ -0,0 +1,62 @@ +use crate::{ + grid::records::{Records, RecordsMut}, + settings::TableOption, +}; + +/// Set a tab size. +/// +/// The size is used in order to calculate width correctly. +/// +/// Default value is 4 (basically 1 '\t' equals 4 spaces). +/// +/// IMPORTANT: The tab character might be not present in output, +/// it might be replaced by spaces. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::formatting::TabSize}; +/// +/// let text = "Some\ttext\t\twith \\tabs"; +/// +/// let mut table = Table::new([text]); +/// table.with(TabSize::new(4)); +/// +/// assert_eq!( +/// table.to_string(), +/// "+--------------------------------+\n\ +/// | &str |\n\ +/// +--------------------------------+\n\ +/// | Some text with \\tabs |\n\ +/// +--------------------------------+" +/// ) +/// ``` +#[derive(Debug, Default, Clone)] +pub struct TabSize(usize); + +impl TabSize { + /// Creates new [`TabSize`] object. + pub fn new(size: usize) -> Self { + Self(size) + } +} + +impl<R, D, C> TableOption<R, D, C> for TabSize +where + for<'a> &'a R: Records, + R: RecordsMut<String>, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let mut list = vec![]; + for (row, cells) in records.iter_rows().into_iter().enumerate() { + for (col, text) in cells.into_iter().enumerate() { + let text = text.as_ref().replace('\t', &" ".repeat(self.0)); + list.push(((row, col), text)); + } + } + + for (pos, text) in list { + records.set(pos, text); + } + } +} diff --git a/vendor/tabled/src/settings/formatting/trim_strategy.rs b/vendor/tabled/src/settings/formatting/trim_strategy.rs new file mode 100644 index 000000000..64ec7e10a --- /dev/null +++ b/vendor/tabled/src/settings/formatting/trim_strategy.rs @@ -0,0 +1,118 @@ +use crate::{ + grid::config::ColoredConfig, + grid::config::Entity, + settings::{CellOption, TableOption}, +}; + +/// `TrimStrategy` determines if it's allowed to use empty space while doing [`Alignment`]. +/// +/// # Examples +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Style, Modify, Alignment, object::Segment, +/// formatting::{TrimStrategy, AlignmentStrategy} +/// } +/// }; +/// +/// let mut table = Table::new(&[" Hello World"]); +/// table +/// .with(Style::modern()) +/// .with( +/// Modify::new(Segment::all()) +/// .with(Alignment::left()) +/// .with(TrimStrategy::Horizontal) +/// ); +/// +/// // Note that nothing was changed exactly. +/// +/// assert_eq!( +/// table.to_string(), +/// "┌────────────────┐\n\ +/// │ &str │\n\ +/// ├────────────────┤\n\ +/// │ Hello World │\n\ +/// └────────────────┘" +/// ); +/// +/// // To trim lines you would need also set [`AlignmentStrategy`]. +/// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); +/// +/// assert_eq!( +/// table.to_string(), +/// "┌────────────────┐\n\ +/// │ &str │\n\ +/// ├────────────────┤\n\ +/// │ Hello World │\n\ +/// └────────────────┘" +/// ); +/// +/// let mut table = Table::new(&[" \n\n\n Hello World"]); +/// table +/// .with(Style::modern()) +/// .with( +/// Modify::new(Segment::all()) +/// .with(Alignment::center()) +/// .with(Alignment::top()) +/// .with(TrimStrategy::Vertical) +/// ); +/// +/// assert_eq!( +/// table.to_string(), +/// "┌─────────────────┐\n\ +/// │ &str │\n\ +/// ├─────────────────┤\n\ +/// │ Hello World │\n\ +/// │ │\n\ +/// │ │\n\ +/// │ │\n\ +/// └─────────────────┘" +/// ); +/// ``` +/// +/// [`Alignment`]: crate::settings::Alignment +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum TrimStrategy { + /// Allow vertical trim. + Vertical, + /// Allow horizontal trim. + Horizontal, + /// Allow horizontal and vertical trim. + Both, + /// Doesn't allow any trim. + None, +} + +impl<R> CellOption<R, ColoredConfig> for TrimStrategy { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let mut formatting = *cfg.get_formatting(entity); + + // todo: could be changed to be a struct an enum like consts in `impl` block. + match self { + TrimStrategy::Vertical => { + formatting.vertical_trim = true; + } + TrimStrategy::Horizontal => { + formatting.horizontal_trim = true; + } + TrimStrategy::Both => { + formatting.vertical_trim = true; + formatting.horizontal_trim = true; + } + TrimStrategy::None => { + formatting.vertical_trim = false; + formatting.horizontal_trim = false; + } + } + + cfg.set_formatting(entity, formatting); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for TrimStrategy { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + <Self as CellOption<_, _>>::change(self, records, cfg, Entity::Global) + } +} diff --git a/vendor/tabled/src/settings/height/cell_height_increase.rs b/vendor/tabled/src/settings/height/cell_height_increase.rs new file mode 100644 index 000000000..39887f9f6 --- /dev/null +++ b/vendor/tabled/src/settings/height/cell_height_increase.rs @@ -0,0 +1,98 @@ +use crate::{ + grid::config::ColoredConfig, + grid::config::Entity, + grid::dimension::CompleteDimensionVecRecords, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + grid::util::string::count_lines, + settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption}, +}; + +use super::TableHeightIncrease; + +/// A modification for cell/table to increase its height. +/// +/// If used for a [`Table`] [`PriorityNone`] is used. +/// +/// [`PriorityNone`]: crate::settings::peaker::PriorityNone +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CellHeightIncrease<W = usize> { + height: W, +} + +impl<W> CellHeightIncrease<W> { + /// Creates a new object of the structure. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { height } + } + + /// The priority makes scence only for table, so the function + /// converts it to [`TableHeightIncrease`] with a given priority. + pub fn priority<P>(self) -> TableHeightIncrease<W, P> + where + P: Peaker, + W: Measurement<Height>, + { + TableHeightIncrease::new(self.height).priority::<P>() + } +} + +impl<W, R> CellOption<R, ColoredConfig> for CellHeightIncrease<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let height = self.height.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + + let cell_height = count_lines(text); + if cell_height >= height { + continue; + } + + let content = add_lines(text, height - cell_height); + records.set(pos, content); + } + } +} + +impl<R, W> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for CellHeightIncrease<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let height = self.height.measure(&*records, cfg); + TableHeightIncrease::new(height).change(records, cfg, dims) + } +} + +fn add_lines(s: &str, n: usize) -> String { + let mut text = String::with_capacity(s.len() + n); + text.push_str(s); + text.extend(std::iter::repeat('\n').take(n)); + + text +} diff --git a/vendor/tabled/src/settings/height/cell_height_limit.rs b/vendor/tabled/src/settings/height/cell_height_limit.rs new file mode 100644 index 000000000..788f0300c --- /dev/null +++ b/vendor/tabled/src/settings/height/cell_height_limit.rs @@ -0,0 +1,103 @@ +use crate::{ + grid::{ + config::{ColoredConfig, Entity}, + dimension::CompleteDimensionVecRecords, + records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + util::string::{count_lines, get_lines}, + }, + settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption}, +}; + +use super::table_height_limit::TableHeightLimit; + +/// A modification for cell/table to increase its height. +/// +/// If used for a [`Table`] [`PriorityNone`] is used. +/// +/// [`PriorityNone`]: crate::settings::peaker::PriorityNone +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CellHeightLimit<W = usize> { + height: W, +} + +impl<W> CellHeightLimit<W> { + /// Constructs a new object. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { height } + } + + /// Set's a priority by which the limit logic will be applied. + pub fn priority<P>(self) -> TableHeightLimit<W, P> + where + P: Peaker, + W: Measurement<Height>, + { + TableHeightLimit::new(self.height).priority::<P>() + } +} + +impl<W, R> CellOption<R, ColoredConfig> for CellHeightLimit<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let height = self.height.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + let count_lines = count_lines(text); + + if count_lines <= height { + continue; + } + + let content = limit_lines(text, height); + records.set(pos, content); + } + } +} + +impl<R, W> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for CellHeightLimit<W> +where + W: Measurement<Height>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let height = self.height.measure(&*records, cfg); + TableHeightLimit::new(height).change(records, cfg, dims) + } +} + +fn limit_lines(s: &str, n: usize) -> String { + let mut text = String::new(); + for (i, line) in get_lines(s).take(n).enumerate() { + if i > 0 { + text.push('\n'); + } + + text.push_str(&line); + } + + text +} diff --git a/vendor/tabled/src/settings/height/height_list.rs b/vendor/tabled/src/settings/height/height_list.rs new file mode 100644 index 000000000..7861dd4d2 --- /dev/null +++ b/vendor/tabled/src/settings/height/height_list.rs @@ -0,0 +1,47 @@ +use std::iter::FromIterator; + +use crate::{ + grid::dimension::CompleteDimensionVecRecords, + grid::records::{ExactRecords, Records}, + settings::TableOption, +}; + +/// A structure used to set [`Table`] height via a list of rows heights. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct HeightList { + list: Vec<usize>, +} + +impl HeightList { + /// Creates a new object. + pub fn new(list: Vec<usize>) -> Self { + Self { list } + } +} + +impl From<Vec<usize>> for HeightList { + fn from(list: Vec<usize>) -> Self { + Self::new(list) + } +} + +impl FromIterator<usize> for HeightList { + fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self { + Self::new(iter.into_iter().collect()) + } +} + +impl<R, C> TableOption<R, CompleteDimensionVecRecords<'static>, C> for HeightList +where + R: ExactRecords + Records, +{ + fn change(self, records: &mut R, _: &mut C, dims: &mut CompleteDimensionVecRecords<'static>) { + if self.list.len() < records.count_rows() { + return; + } + + let _ = dims.set_heights(self.list); + } +} diff --git a/vendor/tabled/src/settings/height/mod.rs b/vendor/tabled/src/settings/height/mod.rs new file mode 100644 index 000000000..ad9caff77 --- /dev/null +++ b/vendor/tabled/src/settings/height/mod.rs @@ -0,0 +1,228 @@ +//! The module contains [`Height`] structure which is responsible for a table and cell height. + +mod cell_height_increase; +mod cell_height_limit; +mod height_list; +mod table_height_increase; +mod table_height_limit; +mod util; + +use crate::settings::measurement::Measurement; + +pub use cell_height_increase::CellHeightIncrease; +pub use cell_height_limit::CellHeightLimit; +pub use height_list::HeightList; +pub use table_height_increase::TableHeightIncrease; +pub use table_height_limit::TableHeightLimit; + +/// Height is a abstract factory for height settings. +/// +/// # Example +/// +/// ``` +/// use tabled::{Table, settings::{Height, Settings}}; +/// +/// let data = vec![ +/// ("Some data", "here", "and here"), +/// ("Some data on a next", "line", "right here"), +/// ]; +/// +/// let table = Table::new(data) +/// .with(Settings::new(Height::limit(10), Height::increase(10))) +/// .to_string(); +/// +/// assert_eq!( +/// table, +/// "+---------------------+------+------------+\n\ +/// | &str | &str | &str |\n\ +/// | | | |\n\ +/// +---------------------+------+------------+\n\ +/// | Some data | here | and here |\n\ +/// | | | |\n\ +/// +---------------------+------+------------+\n\ +/// | Some data on a next | line | right here |\n\ +/// | | | |\n\ +/// +---------------------+------+------------+", +/// ) +/// ``` +#[derive(Debug)] +pub struct Height; + +impl Height { + /// Create [`CellHeightIncrease`] to set a table/cell height. + /// + /// # Example + /// + /// ## Cell height + /// + /// ``` + /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; + /// + /// let data = vec![ + /// ("Some data", "here", "and here"), + /// ("Some data on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Modify::new(Columns::first()).with(Height::increase(5))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+---------------------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data | here | and here |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data on a next | line | right here |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+" + /// ) + /// ``` + /// + /// ## Table height + /// + /// ``` + /// use tabled::{Table, settings::Height}; + /// + /// let data = vec![ + /// ("Some data", "here", "and here"), + /// ("Some data on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Height::increase(10)) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+---------------------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data | here | and here |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+\n\ + /// | Some data on a next | line | right here |\n\ + /// | | | |\n\ + /// +---------------------+------+------------+", + /// ) + /// ``` + pub fn increase<W: Measurement<Height>>(width: W) -> CellHeightIncrease<W> { + CellHeightIncrease::new(width) + } + + /// Create [`CellHeightLimit`] to set a table/cell height. + /// + /// # Example + /// + /// ## Cell height + /// + /// ``` + /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Modify::new(Columns::first()).with(Height::limit(1))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// +------+------+------------+\n\ + /// | Some | here | and here |\n\ + /// +------+------+------------+\n\ + /// | Some | line | right here |\n\ + /// +------+------+------------+" + /// ) + /// ``` + /// + /// ## Table height + /// + /// ``` + /// use tabled::{Table, settings::Height}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(&data) + /// .with(Height::limit(6)) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+------+------+------------+\n\ + /// +------+------+------------+\n\ + /// | Some | here | and here |\n\ + /// +------+------+------------+\n\ + /// | Some | line | right here |\n\ + /// +------+------+------------+", + /// ); + /// + /// let table = Table::new(&data) + /// .with(Height::limit(1)) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+--+--+--+\n\ + /// +--+--+--+\n\ + /// +--+--+--+\n\ + /// +--+--+--+", + /// ); + /// ``` + pub fn limit<W: Measurement<Height>>(width: W) -> CellHeightLimit<W> { + CellHeightLimit::new(width) + } + + /// Create [`HeightList`] to set a table height to a constant list of row heights. + /// + /// Notice if you provide a list with `.len()` less than `Table::count_rows` then it will have no affect. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Height::list([1, 0, 2])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+----------------+------+------------+\n\ + /// | &str | &str | &str |\n\ + /// +----------------+------+------------+\n\ + /// +----------------+------+------------+\n\ + /// | Some | line | right here |\n\ + /// | data on a next | | |\n\ + /// +----------------+------+------------+", + /// ) + /// ``` + pub fn list<I: IntoIterator<Item = usize>>(rows: I) -> HeightList { + HeightList::new(rows.into_iter().collect()) + } +} diff --git a/vendor/tabled/src/settings/height/table_height_increase.rs b/vendor/tabled/src/settings/height/table_height_increase.rs new file mode 100644 index 000000000..f727bf041 --- /dev/null +++ b/vendor/tabled/src/settings/height/table_height_increase.rs @@ -0,0 +1,90 @@ +use crate::{ + grid::{ + config::ColoredConfig, + dimension::CompleteDimensionVecRecords, + records::{ExactRecords, PeekableRecords, Records}, + }, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + Height, TableOption, + }, +}; + +use super::util::get_table_height; + +/// A modification of a table to increase the table height. +#[derive(Debug, Clone)] +pub struct TableHeightIncrease<W = usize, P = PriorityNone> { + height: W, + priority: P, +} + +impl<W> TableHeightIncrease<W, PriorityNone> { + /// Creates a new object. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { + height, + priority: PriorityNone::default(), + } + } + + /// Sets a different priority logic. + pub fn priority<P>(self) -> TableHeightIncrease<W, P> + where + P: Peaker, + { + TableHeightIncrease { + priority: P::create(), + height: self.height, + } + } +} + +impl<R, W, P> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for TableHeightIncrease<W, P> +where + W: Measurement<Height>, + P: Peaker + Clone, + R: Records + ExactRecords + PeekableRecords, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let height = self.height.measure(&*records, cfg); + let (total, mut heights) = get_table_height(&*records, cfg); + if total >= height { + return; + } + + get_increase_list(&mut heights, height, total, self.priority); + + let _ = dims.set_heights(heights); + } +} + +fn get_increase_list<P>(list: &mut [usize], total: usize, mut current: usize, mut peaker: P) +where + P: Peaker, +{ + while current != total { + let col = match peaker.peak(&[], list) { + Some(col) => col, + None => break, + }; + + list[col] += 1; + current += 1; + } +} diff --git a/vendor/tabled/src/settings/height/table_height_limit.rs b/vendor/tabled/src/settings/height/table_height_limit.rs new file mode 100644 index 000000000..2a3d19bb7 --- /dev/null +++ b/vendor/tabled/src/settings/height/table_height_limit.rs @@ -0,0 +1,123 @@ +use crate::{ + grid::{ + config::ColoredConfig, + dimension::CompleteDimensionVecRecords, + records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + util::string::{count_lines, get_lines}, + }, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + Height, TableOption, + }, +}; + +use super::util::get_table_height; + +/// A modification of a table to decrease the table height. +#[derive(Debug)] +pub struct TableHeightLimit<W = usize, P = PriorityNone> { + height: W, + priority: P, +} + +impl<W> TableHeightLimit<W, PriorityNone> { + /// Creates a new object. + pub fn new(height: W) -> Self + where + W: Measurement<Height>, + { + Self { + height, + priority: PriorityNone::default(), + } + } + + /// Sets a different priority logic. + pub fn priority<P>(self) -> TableHeightLimit<W, P> + where + P: Peaker, + { + TableHeightLimit { + priority: P::create(), + height: self.height, + } + } +} + +impl<R, W, P> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for TableHeightLimit<W, P> +where + W: Measurement<Height>, + P: Peaker + Clone, + R: ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let count_rows = records.count_rows(); + let count_cols = (&*records).count_columns(); + + if count_rows == 0 || count_cols == 0 { + return; + } + + let height = self.height.measure(&*records, cfg); + let (total, mut heights) = get_table_height(&*records, cfg); + if total <= height { + return; + } + + decrease_list(&mut heights, total, height, self.priority); + + for (row, &height) in heights.iter().enumerate() { + for col in 0..count_cols { + let text = records.get_text((row, col)); + let count_lines = count_lines(text); + + if count_lines <= height { + continue; + } + + let text = limit_lines(text, height); + + records.set((row, col), text); + } + } + + let _ = dims.set_heights(heights); + } +} + +fn decrease_list<P>(list: &mut [usize], total: usize, mut value: usize, mut peaker: P) +where + P: Peaker, +{ + while value != total { + let p = peaker.peak(&[], list); + let row = match p { + Some(row) => row, + None => break, + }; + + list[row] -= 1; + value += 1; + } +} + +fn limit_lines(s: &str, n: usize) -> String { + let mut text = String::new(); + for (i, line) in get_lines(s).take(n).enumerate() { + if i > 0 { + text.push('\n'); + } + + text.push_str(&line); + } + + text +} diff --git a/vendor/tabled/src/settings/height/util.rs b/vendor/tabled/src/settings/height/util.rs new file mode 100644 index 000000000..c6917d925 --- /dev/null +++ b/vendor/tabled/src/settings/height/util.rs @@ -0,0 +1,22 @@ +use crate::grid::{ + config::SpannedConfig, + dimension::SpannedGridDimension, + records::{ExactRecords, Records}, +}; + +pub(crate) fn get_table_height<R: Records + ExactRecords>( + records: R, + cfg: &SpannedConfig, +) -> (usize, Vec<usize>) { + let count_horizontals = cfg.count_horizontal(records.count_rows()); + + let margin = cfg.get_margin(); + let margin_size = margin.top.size + margin.bottom.size; + + let list = SpannedGridDimension::height(records, cfg); + let total = list.iter().sum::<usize>(); + + let total = total + count_horizontals + margin_size; + + (total, list) +} diff --git a/vendor/tabled/src/settings/highlight/mod.rs b/vendor/tabled/src/settings/highlight/mod.rs new file mode 100644 index 000000000..57df867b3 --- /dev/null +++ b/vendor/tabled/src/settings/highlight/mod.rs @@ -0,0 +1,452 @@ +//! This module contains a [`Highlight`] primitive, which helps +//! changing a [`Border`] style of any segment on a [`Table`]. +//! +//! [`Table`]: crate::Table + +use std::collections::HashSet; + +use crate::{ + grid::{ + config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::{object::Object, style::BorderColor, Border, TableOption}, +}; + +/// Highlight modifies a table style by changing a border of a target [`Table`] segment. +/// +/// # Example +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{Highlight, Border, Style, object::Segment} +/// }; +/// +/// let data = [ +/// ("ELF", "Extensible Linking Format", true), +/// ("DWARF", "", true), +/// ("PE", "Portable Executable", false), +/// ]; +/// +/// let table = Table::new(data.iter().enumerate()) +/// .with(Style::markdown()) +/// .with(Highlight::new(Segment::all(), Border::default().top('^').bottom('v'))) +/// .to_string(); +/// +/// assert_eq!( +/// table, +/// concat!( +/// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n", +/// "| usize | &str | &str | bool |\n", +/// "|-------|-------|---------------------------|-------|\n", +/// "| 0 | ELF | Extensible Linking Format | true |\n", +/// "| 1 | DWARF | | true |\n", +/// "| 2 | PE | Portable Executable | false |\n", +/// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ", +/// ), +/// ); +/// ``` +/// +/// It's possible to use [`Highlight`] for many kinds of figures. +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Highlight, Border, Style, +/// object::{Segment, Object} +/// } +/// }; +/// +/// let data = [ +/// ("ELF", "Extensible Linking Format", true), +/// ("DWARF", "", true), +/// ("PE", "Portable Executable", false), +/// ]; +/// +/// let table = Table::new(data.iter().enumerate()) +/// .with(Style::markdown()) +/// .with(Highlight::new(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*'))) +/// .to_string(); +/// +/// println!("{}", table); +/// +/// assert_eq!( +/// table, +/// concat!( +/// " ***************************** \n", +/// "| usize | &str * &str * bool |\n", +/// "|-------*********---------------------------*********\n", +/// "| 0 * ELF | Extensible Linking Format | true *\n", +/// "********* *\n", +/// "* 1 | DWARF | | true *\n", +/// "* *\n", +/// "* 2 | PE | Portable Executable | false *\n", +/// "*****************************************************", +/// ), +/// ); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Highlight<O> { + target: O, + border: Border, +} + +// todo: Add BorderColor. + +impl<O> Highlight<O> { + /// Build a new instance of [`Highlight`] + /// + /// BE AWARE: if target exceeds boundaries it may panic. + pub fn new(target: O, border: Border) -> Self { + Self { target, border } + } +} + +impl<O> Highlight<O> { + /// Build a new instance of [`HighlightColored`] + pub fn colored(target: O, border: BorderColor) -> HighlightColored<O> { + HighlightColored { target, border } + } +} + +impl<O, R, D> TableOption<R, D, ColoredConfig> for Highlight<O> +where + O: Object<R>, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let cells = self.target.cells(records); + let segments = split_segments(cells, count_rows, count_cols); + + for sector in segments { + set_border(cfg, §or, self.border); + } + } +} + +/// A [`Highlight`] object which works with a [`BorderColored`] +/// +/// [`BorderColored`]: crate::settings::style::BorderColor +#[derive(Debug)] +pub struct HighlightColored<O> { + target: O, + border: BorderColor, +} + +impl<O, R, D> TableOption<R, D, ColoredConfig> for HighlightColored<O> +where + O: Object<R>, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + let cells = self.target.cells(records); + let segments = split_segments(cells, count_rows, count_cols); + + for sector in segments { + set_border_color(cfg, sector, &self.border); + } + } +} + +fn set_border_color( + cfg: &mut SpannedConfig, + sector: HashSet<(usize, usize)>, + border: &BorderColor, +) { + if sector.is_empty() { + return; + } + let color = border.clone().into(); + for &(row, col) in §or { + let border = build_cell_border(§or, (row, col), &color); + cfg.set_border_color((row, col), border); + } +} + +fn split_segments( + cells: impl Iterator<Item = Entity>, + count_rows: usize, + count_cols: usize, +) -> Vec<HashSet<(usize, usize)>> { + let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new(); + for entity in cells { + for cell in entity.iter(count_rows, count_cols) { + let found_segment = segments + .iter_mut() + .find(|s| s.iter().any(|&c| is_cell_connected(cell, c))); + + match found_segment { + Some(segment) => { + let _ = segment.insert(cell); + } + None => { + let mut segment = HashSet::new(); + let _ = segment.insert(cell); + segments.push(segment); + } + } + } + } + + let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new(); + while !segments.is_empty() { + let mut segment = segments.remove(0); + + let mut i = 0; + while i < segments.len() { + if is_segment_connected(&segment, &segments[i]) { + segment.extend(&segments[i]); + let _ = segments.remove(i); + } else { + i += 1; + } + } + + squashed_segments.push(segment); + } + + squashed_segments +} + +fn is_cell_connected((row1, col1): (usize, usize), (row2, col2): (usize, usize)) -> bool { + if col1 == col2 && row1 == row2 + 1 { + return true; + } + + if col1 == col2 && (row2 > 0 && row1 == row2 - 1) { + return true; + } + + if row1 == row2 && col1 == col2 + 1 { + return true; + } + + if row1 == row2 && (col2 > 0 && col1 == col2 - 1) { + return true; + } + + false +} + +fn is_segment_connected( + segment1: &HashSet<(usize, usize)>, + segment2: &HashSet<(usize, usize)>, +) -> bool { + for &cell1 in segment1.iter() { + for &cell2 in segment2.iter() { + if is_cell_connected(cell1, cell2) { + return true; + } + } + } + + false +} + +fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<(usize, usize)>, border: Border) { + if sector.is_empty() { + return; + } + + let border = border.into(); + for &pos in sector { + let border = build_cell_border(sector, pos, &border); + cfg.set_border(pos, border); + } +} + +fn build_cell_border<T>( + sector: &HashSet<(usize, usize)>, + (row, col): Position, + border: &GridBorder<T>, +) -> GridBorder<T> +where + T: Default + Clone, +{ + let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col); + let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col); + let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col); + let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col); + + let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col); + let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col); + let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col); + let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col); + + let mut cell_border = GridBorder::default(); + if let Some(c) = border.top.clone() { + if !cell_has_top_neighbor { + cell_border.top = Some(c.clone()); + + if cell_has_right_neighbor && !this_has_right_top_neighbor { + cell_border.right_top_corner = Some(c); + } + } + } + if let Some(c) = border.bottom.clone() { + if !cell_has_bottom_neighbor { + cell_border.bottom = Some(c.clone()); + + if cell_has_right_neighbor && !this_has_right_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.left.clone() { + if !cell_has_left_neighbor { + cell_border.left = Some(c.clone()); + + if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor { + cell_border.left_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.right.clone() { + if !cell_has_right_neighbor { + cell_border.right = Some(c.clone()); + + if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + } + if let Some(c) = border.left_top_corner.clone() { + if !cell_has_left_neighbor && !cell_has_top_neighbor { + cell_border.left_top_corner = Some(c); + } + } + if let Some(c) = border.left_bottom_corner.clone() { + if !cell_has_left_neighbor && !cell_has_bottom_neighbor { + cell_border.left_bottom_corner = Some(c); + } + } + if let Some(c) = border.right_top_corner.clone() { + if !cell_has_right_neighbor && !cell_has_top_neighbor { + cell_border.right_top_corner = Some(c); + } + } + if let Some(c) = border.right_bottom_corner.clone() { + if !cell_has_right_neighbor && !cell_has_bottom_neighbor { + cell_border.right_bottom_corner = Some(c); + } + } + { + if !cell_has_bottom_neighbor { + if !cell_has_left_neighbor && this_has_left_top_neighbor { + if let Some(c) = border.right_top_corner.clone() { + cell_border.left_top_corner = Some(c); + } + } + + if cell_has_left_neighbor && this_has_left_bottom_neighbor { + if let Some(c) = border.left_top_corner.clone() { + cell_border.left_bottom_corner = Some(c); + } + } + + if !cell_has_right_neighbor && this_has_right_top_neighbor { + if let Some(c) = border.left_top_corner.clone() { + cell_border.right_top_corner = Some(c); + } + } + + if cell_has_right_neighbor && this_has_right_bottom_neighbor { + if let Some(c) = border.right_top_corner.clone() { + cell_border.right_bottom_corner = Some(c); + } + } + } + + if !cell_has_top_neighbor { + if !cell_has_left_neighbor && this_has_left_bottom_neighbor { + if let Some(c) = border.right_bottom_corner.clone() { + cell_border.left_bottom_corner = Some(c); + } + } + + if cell_has_left_neighbor && this_has_left_top_neighbor { + if let Some(c) = border.left_bottom_corner.clone() { + cell_border.left_top_corner = Some(c); + } + } + + if !cell_has_right_neighbor && this_has_right_bottom_neighbor { + if let Some(c) = border.left_bottom_corner.clone() { + cell_border.right_bottom_corner = Some(c); + } + } + + if cell_has_right_neighbor && this_has_right_top_neighbor { + if let Some(c) = border.right_bottom_corner.clone() { + cell_border.right_top_corner = Some(c); + } + } + } + } + + cell_border +} + +fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && sector.contains(&(row - 1, col)) +} + +fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row + 1, col)) +} + +fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + col > 0 && sector.contains(&(row, col - 1)) +} + +fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row, col + 1)) +} + +fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && col > 0 && sector.contains(&(row - 1, col - 1)) +} + +fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + row > 0 && sector.contains(&(row - 1, col + 1)) +} + +fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + col > 0 && sector.contains(&(row + 1, col - 1)) +} + +fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { + sector.contains(&(row + 1, col + 1)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_connected() { + assert!(is_cell_connected((0, 0), (0, 1))); + assert!(is_cell_connected((0, 0), (1, 0))); + assert!(!is_cell_connected((0, 0), (1, 1))); + + assert!(is_cell_connected((0, 1), (0, 0))); + assert!(is_cell_connected((1, 0), (0, 0))); + assert!(!is_cell_connected((1, 1), (0, 0))); + + assert!(is_cell_connected((1, 1), (0, 1))); + assert!(is_cell_connected((1, 1), (1, 0))); + assert!(is_cell_connected((1, 1), (2, 1))); + assert!(is_cell_connected((1, 1), (1, 2))); + assert!(!is_cell_connected((1, 1), (1, 1))); + } +} diff --git a/vendor/tabled/src/settings/locator/mod.rs b/vendor/tabled/src/settings/locator/mod.rs new file mode 100644 index 000000000..b48391bb7 --- /dev/null +++ b/vendor/tabled/src/settings/locator/mod.rs @@ -0,0 +1,202 @@ +//! The module contains a [`Locator`] trait and implementations for it. + +use core::ops::Bound; +use std::{ + iter::{self, Once}, + ops::{Range, RangeBounds}, +}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, PeekableRecords, Records}, + settings::object::{ + Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Object, Row, Rows, + }, +}; + +/// Locator is an interface which searches for a particular thing in the [`Records`], +/// and returns coordinate of the foundings if any. +pub trait Locator<Records> { + /// A coordinate of the finding. + type Coordinate; + /// An iterator of the coordinates. + /// If it's empty it's considered that nothing is found. + type IntoIter: IntoIterator<Item = Self::Coordinate>; + + /// Search for the thing in [`Records`], returning a list of coordinates. + fn locate(&mut self, records: Records) -> Self::IntoIter; +} + +impl<B, R> Locator<R> for Columns<B> +where + B: RangeBounds<usize>, + R: Records, +{ + type Coordinate = usize; + type IntoIter = Range<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + let range = self.get_range(); + let max = records.count_columns(); + let (from, to) = bounds_to_usize(range.start_bound(), range.end_bound(), max); + + from..to + } +} + +impl<R> Locator<R> for Column { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once((*self).into()) + } +} + +impl<R> Locator<R> for FirstColumn { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once(0) + } +} + +impl<R> Locator<R> for LastColumn +where + R: Records, +{ + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + if records.count_columns() > 0 { + iter::once(records.count_columns() - 1) + } else { + iter::once(0) + } + } +} + +impl<B, R> Locator<R> for Rows<B> +where + R: Records, + B: RangeBounds<usize>, +{ + type Coordinate = usize; + type IntoIter = Range<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + let (from, to) = bounds_to_usize( + self.get_range().start_bound(), + self.get_range().end_bound(), + records.count_columns(), + ); + + from..to + } +} + +impl<R> Locator<R> for Row { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once((*self).into()) + } +} + +impl<R> Locator<R> for FirstRow { + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, _: R) -> Self::IntoIter { + iter::once(0) + } +} + +impl<R> Locator<R> for LastRow +where + R: ExactRecords, +{ + type Coordinate = usize; + type IntoIter = Once<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + if records.count_rows() > 0 { + iter::once(records.count_rows() - 1) + } else { + iter::once(0) + } + } +} + +/// The structure is an implementation of [`Locator`] to search for a column by it's name. +/// A name is considered be a value in a first row. +/// +/// So even if in reality there's no header, the first row will be considered to be one. +#[derive(Debug, Clone, Copy)] +pub struct ByColumnName<S>(S); + +impl<S> ByColumnName<S> { + /// Constructs a new object of the structure. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + Self(text) + } +} + +impl<R, S> Locator<R> for ByColumnName<S> +where + S: AsRef<str>, + R: Records + ExactRecords + PeekableRecords, +{ + type Coordinate = usize; + type IntoIter = Vec<usize>; + + fn locate(&mut self, records: R) -> Self::IntoIter { + // todo: can be optimized by creating Iterator + (0..records.count_columns()) + .filter(|col| records.get_text((0, *col)) == self.0.as_ref()) + .collect::<Vec<_>>() + } +} + +impl<S, R> Object<R> for ByColumnName<S> +where + S: AsRef<str>, + R: Records + PeekableRecords + ExactRecords, +{ + type Iter = std::vec::IntoIter<Entity>; + + fn cells(&self, records: &R) -> Self::Iter { + // todo: can be optimized by creating Iterator + (0..records.count_columns()) + .filter(|col| records.get_text((0, *col)) == self.0.as_ref()) + .map(Entity::Column) + .collect::<Vec<_>>() + .into_iter() + } +} + +fn bounds_to_usize( + left: Bound<&usize>, + right: Bound<&usize>, + count_elements: usize, +) -> (usize, usize) { + match (left, right) { + (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), + (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), + (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), + (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), + (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), + (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), + (Bound::Excluded(_), Bound::Unbounded) + | (Bound::Excluded(_), Bound::Included(_)) + | (Bound::Excluded(_), Bound::Excluded(_)) => { + unreachable!("A start bound can't be excluded") + } + } +} diff --git a/vendor/tabled/src/settings/margin/mod.rs b/vendor/tabled/src/settings/margin/mod.rs new file mode 100644 index 000000000..b86a1d3e2 --- /dev/null +++ b/vendor/tabled/src/settings/margin/mod.rs @@ -0,0 +1,137 @@ +//! This module contains a Margin settings of a [`Table`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{settings::{Margin, Style}, Table}; +//! +//! let data = vec!["Hello", "World", "!"]; +//! +//! let mut table = Table::new(data); +//! table.with(Style::markdown()).with(Margin::new(3, 3, 1, 0)); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! " \n", +//! " | &str | \n", +//! " |-------| \n", +//! " | Hello | \n", +//! " | World | \n", +//! " | ! | ", +//! ) +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::{ + color::StaticColor, + config::{CompactConfig, CompactMultilineConfig}, + config::{Indent, Sides}, + }, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::{color::AnsiColor, config::ColoredConfig}; + +/// Margin is responsible for a left/right/top/bottom outer indent of a grid. +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// # use tabled::{settings::Margin, Table}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')); +/// ``` +#[derive(Debug, Clone)] +pub struct Margin<C = StaticColor> { + indent: Sides<Indent>, + colors: Option<Sides<C>>, +} + +impl Margin { + /// Construct's an Margin object. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Margin::fill`] function. + pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { + Self { + indent: Sides::new( + Indent::spaced(left), + Indent::spaced(right), + Indent::spaced(top), + Indent::spaced(bottom), + ), + colors: None, + } + } +} + +impl<Color> Margin<Color> { + /// The function, sets a characters for the margin on an each side. + pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self { + self.indent.left.fill = left; + self.indent.right.fill = right; + self.indent.top.fill = top; + self.indent.bottom.fill = bottom; + self + } + + /// The function, sets a characters for the margin on an each side. + pub fn colorize<C>(self, left: C, right: C, top: C, bottom: C) -> Margin<C> { + Margin { + indent: self.indent, + colors: Some(Sides::new(left, right, top, bottom)), + } + } +} + +#[cfg(feature = "std")] +impl<R, D, C> TableOption<R, D, ColoredConfig> for Margin<C> +where + C: Into<AnsiColor<'static>> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let indent = self.indent; + let margin = Sides::new(indent.left, indent.right, indent.top, indent.bottom); + cfg.set_margin(margin); + + if let Some(colors) = &self.colors { + let margin = Sides::new( + Some(colors.left.clone().into()), + Some(colors.right.clone().into()), + Some(colors.top.clone().into()), + Some(colors.bottom.clone().into()), + ); + cfg.set_margin_color(margin); + } + } +} + +impl<R, D, C> TableOption<R, D, CompactConfig> for Margin<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + *cfg = cfg.set_margin(self.indent); + + if let Some(c) = self.colors { + // todo: make a new method (BECAUSE INTO doesn't work) try_into(); + let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); + *cfg = cfg.set_margin_color(colors); + } + } +} + +impl<R, D, C> TableOption<R, D, CompactMultilineConfig> for Margin<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/measurement/mod.rs b/vendor/tabled/src/settings/measurement/mod.rs new file mode 100644 index 000000000..f93cc6b4e --- /dev/null +++ b/vendor/tabled/src/settings/measurement/mod.rs @@ -0,0 +1,161 @@ +//! The module contains [`Measurement`] trait and its implementations to be used in [`Height`] and [`Width`].; + +use crate::{ + grid::config::SpannedConfig, + grid::dimension::SpannedGridDimension, + grid::records::{ExactRecords, PeekableRecords, Records}, + grid::util::string::{self, string_width_multiline}, + settings::{Height, Width}, +}; + +/// A width value which can be obtained on behalf of [`Table`]. +/// +/// [`Table`]: crate::Table +pub trait Measurement<Attribute> { + /// Returns a measurement value. + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + cfg: &SpannedConfig, + ) -> usize; +} + +impl<T> Measurement<T> for usize { + fn measure<R>(&self, _: R, _: &SpannedConfig) -> usize { + *self + } +} + +/// Max width value. +#[derive(Debug)] +pub struct Max; + +impl Measurement<Width> for Max { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + grid_widths(&records) + .map(|r| r.max().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +impl Measurement<Height> for Max { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + records_heights(&records) + .map(|r| r.max().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +/// Min width value. +#[derive(Debug)] +pub struct Min; + +impl Measurement<Width> for Min { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + grid_widths(&records) + .map(|r| r.min().unwrap_or(0)) + .max() + .unwrap_or(0) + } +} + +impl Measurement<Height> for Min { + fn measure<R: Records + ExactRecords + PeekableRecords>( + &self, + records: R, + _: &SpannedConfig, + ) -> usize { + records_heights(&records) + .map(|r| r.max().unwrap_or(0)) + .min() + .unwrap_or(0) + } +} + +/// Percent from a total table width. +#[derive(Debug)] +pub struct Percent(pub usize); + +impl Measurement<Width> for Percent { + fn measure<R>(&self, records: R, cfg: &SpannedConfig) -> usize + where + R: Records, + { + let (_, total) = get_table_widths_with_total(records, cfg); + (total * self.0) / 100 + } +} + +impl Measurement<Height> for Percent { + fn measure<R>(&self, records: R, cfg: &SpannedConfig) -> usize + where + R: Records + ExactRecords, + { + let (_, total) = get_table_heights_width_total(records, cfg); + (total * self.0) / 100 + } +} + +fn grid_widths<R: Records + ExactRecords + PeekableRecords>( + records: &R, +) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> + '_ { + let (count_rows, count_cols) = (records.count_rows(), records.count_columns()); + (0..count_rows).map(move |row| { + (0..count_cols).map(move |col| string_width_multiline(records.get_text((row, col)))) + }) +} + +fn get_table_widths_with_total<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, usize) +where + R: Records, +{ + let widths = SpannedGridDimension::width(records, cfg); + let total_width = get_table_total_width(&widths, cfg); + (widths, total_width) +} + +fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize { + let total = list.iter().sum::<usize>(); + + total + cfg.count_vertical(list.len()) +} + +fn records_heights<R>(records: &R) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> + '_ +where + R: Records + ExactRecords + PeekableRecords, +{ + (0..records.count_rows()).map(move |row| { + (0..records.count_columns()) + .map(move |col| string::count_lines(records.get_text((row, col)))) + }) +} + +fn get_table_heights_width_total<R>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, usize) +where + R: Records, +{ + let list = SpannedGridDimension::height(records, cfg); + let total = get_table_total_height(&list, cfg); + (list, total) +} + +fn get_table_total_height(list: &[usize], cfg: &SpannedConfig) -> usize { + let total = list.iter().sum::<usize>(); + let counth = cfg.count_horizontal(list.len()); + + total + counth +} diff --git a/vendor/tabled/src/settings/merge/mod.rs b/vendor/tabled/src/settings/merge/mod.rs new file mode 100644 index 000000000..51fa2bbf6 --- /dev/null +++ b/vendor/tabled/src/settings/merge/mod.rs @@ -0,0 +1,196 @@ +//! The module contains a set of methods to merge cells together via [`Span`]s. +//! +//! [`Span`]: crate::settings::span::Span + +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, PeekableRecords, Records}, + settings::TableOption, +}; + +/// Merge to combine duplicates together, using [`Span`]. +/// +/// [`Span`]: crate::settings::span::Span +#[derive(Debug)] +pub struct Merge; + +impl Merge { + /// Vertical merge. + pub fn vertical() -> MergeDuplicatesVertical { + MergeDuplicatesVertical + } + + /// Horizontal merge. + pub fn horizontal() -> MergeDuplicatesHorizontal { + MergeDuplicatesHorizontal + } +} + +/// A modificator for [`Table`] which looks up for duplicates in columns and +/// in case of duplicate merges the cells together using [`Span`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +#[derive(Debug)] +pub struct MergeDuplicatesVertical; + +impl<R, D> TableOption<R, D, ColoredConfig> for MergeDuplicatesVertical +where + R: Records + PeekableRecords + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if count_rows == 0 || count_cols == 0 { + return; + } + + for column in 0..count_cols { + let mut repeat_length = 0; + let mut repeat_value = String::new(); + let mut repeat_is_set = false; + let mut last_is_row_span = false; + for row in (0..count_rows).rev() { + if last_is_row_span { + last_is_row_span = false; + continue; + } + + // we need to mitigate messing existing spans + let is_cell_visible = cfg.is_cell_visible((row, column)); + let is_row_span_cell = cfg.get_column_span((row, column)).is_some(); + + if !repeat_is_set { + if !is_cell_visible { + continue; + } + + if is_row_span_cell { + continue; + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + repeat_is_set = true; + continue; + } + + if is_row_span_cell { + repeat_is_set = false; + last_is_row_span = true; + continue; + } + + if !is_cell_visible { + repeat_is_set = false; + continue; + } + + let text = records.get_text((row, column)); + let is_duplicate = text == repeat_value; + + if is_duplicate { + repeat_length += 1; + continue; + } + + if repeat_length > 1 { + cfg.set_row_span((row + 1, column), repeat_length); + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + } + + if repeat_length > 1 { + cfg.set_row_span((0, column), repeat_length); + } + } + } +} + +/// A modificator for [`Table`] which looks up for duplicates in rows and +/// in case of duplicate merges the cells together using [`Span`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +#[derive(Debug)] +pub struct MergeDuplicatesHorizontal; + +impl<R, D> TableOption<R, D, ColoredConfig> for MergeDuplicatesHorizontal +where + R: Records + PeekableRecords + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if count_rows == 0 || count_cols == 0 { + return; + } + + for row in 0..count_rows { + let mut repeat_length = 0; + let mut repeat_value = String::new(); + let mut repeat_is_set = false; + let mut last_is_col_span = false; + + for column in (0..count_cols).rev() { + if last_is_col_span { + last_is_col_span = false; + continue; + } + + // we need to mitigate messing existing spans + let is_cell_visible = cfg.is_cell_visible((row, column)); + let is_col_span_cell = cfg.get_row_span((row, column)).is_some(); + + if !repeat_is_set { + if !is_cell_visible { + continue; + } + + if is_col_span_cell { + continue; + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + repeat_is_set = true; + continue; + } + + if is_col_span_cell { + repeat_is_set = false; + last_is_col_span = true; + continue; + } + + if !is_cell_visible { + repeat_is_set = false; + continue; + } + + let text = records.get_text((row, column)); + let is_duplicate = text == repeat_value; + + if is_duplicate { + repeat_length += 1; + continue; + } + + if repeat_length > 1 { + cfg.set_column_span((row, column + 1), repeat_length); + } + + repeat_length = 1; + repeat_value = records.get_text((row, column)).to_owned(); + } + + if repeat_length > 1 { + cfg.set_column_span((row, 0), repeat_length); + } + } + } +} diff --git a/vendor/tabled/src/settings/mod.rs b/vendor/tabled/src/settings/mod.rs new file mode 100644 index 000000000..a89037cbd --- /dev/null +++ b/vendor/tabled/src/settings/mod.rs @@ -0,0 +1,135 @@ +//! Module contains various table configuration settings. +//! +//! There 2 types of settings; +//! +//! - [`CellOption`] which can modify only a cell. +//! - [`TableOption`] which can modify table as a whole. +//! +//! [`CellOption`] works on behave of [`Modify`] which is actually a [`TableOption`]. +//! +//! Notice that it's possble to combine settings together by the help of [`Settings`]. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Settings, Style, Padding}}; +//! +//! let table_config = Settings::default() +//! .with(Padding::new(2, 2, 1, 1)) +//! .with(Style::rounded()); +//! +//! let data = [[2023;9]; 3]; +//! +//! let table = Table::new(data).with(table_config).to_string(); +//! +//! assert_eq!( +//! table, +//! "╭────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────╮\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! ├────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ +//! │ │ │ │ │ │ │ │ │ │\n\ +//! ╰────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────╯" +//! ) +//! ``` + +mod cell_option; +mod settings_list; +mod table_option; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod object; + +#[cfg(feature = "std")] +mod modify; + +mod alignment; +mod extract; +mod margin; +mod padding; +mod rotate; + +#[cfg(feature = "std")] +mod color; +#[cfg(feature = "std")] +mod concat; +#[cfg(feature = "std")] +mod duplicate; + +pub mod style; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod disable; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod format; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod formatting; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod height; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod highlight; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod locator; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod measurement; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod merge; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod panel; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod peaker; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +mod shadow; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod span; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod split; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod themes; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod width; + +pub use cell_option::CellOption; +pub use settings_list::{EmptySettings, Settings}; +pub use table_option::TableOption; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use modify::{Modify, ModifyList}; + +pub use self::{ + alignment::Alignment, extract::Extract, margin::Margin, padding::Padding, rotate::Rotate, + style::Style, +}; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + color::Color, concat::Concat, disable::Disable, duplicate::Dup, format::Format, height::Height, + highlight::Highlight, merge::Merge, panel::Panel, shadow::Shadow, span::Span, style::Border, + width::Width, +}; diff --git a/vendor/tabled/src/settings/modify.rs b/vendor/tabled/src/settings/modify.rs new file mode 100644 index 000000000..c712a255e --- /dev/null +++ b/vendor/tabled/src/settings/modify.rs @@ -0,0 +1,81 @@ +use crate::{ + grid::records::{ExactRecords, Records}, + settings::{object::Object, CellOption, Settings, TableOption}, +}; + +/// Modify structure provide an abstraction, to be able to apply +/// a set of [`CellOption`]s to the same object. +/// +/// Be aware that the settings are applied all to a cell at a time. +/// So sometimes you may need to make a several calls of [`Modify`] in order to achieve the desired affect. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Modify<O> { + obj: O, +} + +impl<O> Modify<O> { + /// Creates a new [`Modify`] without any options. + pub const fn new(obj: O) -> Self { + Self { obj } + } + + /// A function which combines together [`Modify::new`] and [`Modify::with`] calls. + pub const fn list<M>(obj: O, next: M) -> ModifyList<O, M> { + ModifyList { + obj, + modifiers: next, + } + } + + /// It's a generic function which stores a [`CellOption`]. + /// + /// IMPORTANT: + /// The function *doesn't* changes a [`Table`]. + /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`]. + /// + /// [`Table`]: crate::Table + /// [`Table::with`]: crate::Table::with + pub fn with<M>(self, next: M) -> ModifyList<O, M> { + ModifyList { + obj: self.obj, + modifiers: next, + } + } +} + +/// This is a container of [`CellOption`]s which are applied to a set [`Object`]. +#[derive(Debug)] +pub struct ModifyList<O, S> { + obj: O, + modifiers: S, +} + +impl<O, M1> ModifyList<O, M1> { + /// With a generic function which stores a [`CellOption`]. + /// + /// IMPORTANT: + /// The function *doesn't* changes a [`Table`]. + /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`]. + /// + /// [`Table`]: crate::Table + /// [`Table::with`]: crate::Table::with + pub fn with<M2>(self, next: M2) -> ModifyList<O, Settings<M1, M2>> { + ModifyList { + obj: self.obj, + modifiers: Settings::new(self.modifiers, next), + } + } +} + +impl<O, M, R, D, C> TableOption<R, D, C> for ModifyList<O, M> +where + O: Object<R>, + M: CellOption<R, C> + Clone, + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { + for entity in self.obj.cells(records) { + self.modifiers.clone().change(records, cfg, entity); + } + } +} diff --git a/vendor/tabled/src/settings/object/cell.rs b/vendor/tabled/src/settings/object/cell.rs new file mode 100644 index 000000000..0b0463c9a --- /dev/null +++ b/vendor/tabled/src/settings/object/cell.rs @@ -0,0 +1,70 @@ +use crate::{ + grid::config::{Entity, Position}, + settings::object::Object, +}; + +/// Cell denotes a particular cell on a [`Table`]. +/// +/// For example such table has 4 cells. +/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). +/// +/// ```text +/// ┌───┬───┐ +/// │ 0 │ 1 │ +/// ├───┼───┤ +/// │ 1 │ 2 │ +/// └───┴───┘ +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct Cell(usize, usize); + +impl Cell { + /// Create new cell structure. + pub fn new(row: usize, col: usize) -> Self { + Self(row, col) + } +} + +impl From<Position> for Cell { + fn from((row, col): Position) -> Self { + Self(row, col) + } +} + +impl<I> Object<I> for Cell { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Cell(self.0, self.1))) + } +} + +impl<I> Object<I> for Position { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Cell(self.0, self.1))) + } +} + +/// An [`Iterator`] which returns an entity once. +#[derive(Debug)] +pub struct EntityOnce { + entity: Option<Entity>, +} + +impl EntityOnce { + pub(crate) const fn new(entity: Option<Entity>) -> Self { + Self { entity } + } +} + +impl Iterator for EntityOnce { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + self.entity.take() + } +} diff --git a/vendor/tabled/src/settings/object/columns.rs b/vendor/tabled/src/settings/object/columns.rs new file mode 100644 index 000000000..bcbe2acf5 --- /dev/null +++ b/vendor/tabled/src/settings/object/columns.rs @@ -0,0 +1,209 @@ +use std::ops::{Add, RangeBounds, Sub}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::{cell::EntityOnce, Object}, +}; + +use super::util::bounds_to_usize; + +/// Column denotes a set of cells on given columns on a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Columns<R> { + range: R, +} + +impl<R> Columns<R> { + /// Returns a new instance of [`Columns`] for a range of columns. + /// + /// If the boundaries are exceeded it may panic. + pub fn new(range: R) -> Self + where + R: RangeBounds<usize>, + { + Self { range } + } + + pub(crate) fn get_range(&self) -> &R { + &self.range + } +} + +impl Columns<()> { + /// Returns a new instance of [`Columns`] for a single column. + /// + /// If the boundaries are exceeded it may panic. + pub fn single(index: usize) -> Column { + Column(index) + } + + /// Returns a new instance of [`Columns`] for a first column. + /// + /// If the boundaries are exceeded the object will produce no cells. + pub fn first() -> FirstColumn { + FirstColumn + } + + /// Returns a new instance of [`Columns`] for a last column. + /// + /// If the boundaries are exceeded the object will produce no cells. + pub fn last() -> LastColumn { + LastColumn + } +} + +impl<I, R> Object<I> for Columns<R> +where + R: RangeBounds<usize>, + I: Records, +{ + type Iter = ColumnsIter; + + fn cells(&self, records: &I) -> Self::Iter { + let max = records.count_columns(); + let start = self.range.start_bound(); + let end = self.range.end_bound(); + let (x, y) = bounds_to_usize(start, end, max); + + ColumnsIter::new(x, y) + } +} + +/// `FirstColumn` represents the first column on a grid. +#[derive(Debug)] +pub struct FirstColumn; + +impl<I> Object<I> for FirstColumn +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_rows() == 0 || records.count_columns() == 0 { + return EntityOnce::new(None); + } + + EntityOnce::new(Some(Entity::Column(0))) + } +} + +impl Add<usize> for FirstColumn { + type Output = Column; + + fn add(self, rhs: usize) -> Self::Output { + Column(rhs) + } +} + +/// `LastColumn` represents the last column on a grid. +#[derive(Debug)] +pub struct LastColumn; + +impl<I> Object<I> for LastColumn +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_rows() == 0 || records.count_columns() == 0 { + return EntityOnce::new(None); + } + + let col = records.count_columns().saturating_sub(1); + EntityOnce::new(Some(Entity::Column(col))) + } +} + +impl Sub<usize> for LastColumn { + type Output = LastColumnOffset; + + fn sub(self, rhs: usize) -> Self::Output { + LastColumnOffset { offset: rhs } + } +} + +/// Column represents a single column on a grid. +#[derive(Debug, Clone, Copy)] +pub struct Column(usize); + +impl<I> Object<I> for Column { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Column(self.0))) + } +} + +impl From<usize> for Column { + fn from(i: usize) -> Self { + Self(i) + } +} + +impl From<Column> for usize { + fn from(val: Column) -> Self { + val.0 + } +} + +/// `LastColumnOffset` represents a single column on a grid indexed via offset from the last column. +#[derive(Debug)] +pub struct LastColumnOffset { + offset: usize, +} + +impl<I> Object<I> for LastColumnOffset +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_rows() == 0 || records.count_columns() == 0 { + return EntityOnce::new(None); + } + + let col = records.count_columns().saturating_sub(1); + if self.offset > col { + return EntityOnce::new(None); + } + + let col = col - self.offset; + EntityOnce::new(Some(Entity::Column(col))) + } +} + +/// An [`Iterator`] which goes goes over columns of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct ColumnsIter { + start: usize, + end: usize, +} + +impl ColumnsIter { + const fn new(start: usize, end: usize) -> Self { + Self { start, end } + } +} + +impl Iterator for ColumnsIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if self.start >= self.end { + return None; + } + + let col = self.start; + self.start += 1; + + Some(Entity::Column(col)) + } +} diff --git a/vendor/tabled/src/settings/object/frame.rs b/vendor/tabled/src/settings/object/frame.rs new file mode 100644 index 000000000..caeb10640 --- /dev/null +++ b/vendor/tabled/src/settings/object/frame.rs @@ -0,0 +1,69 @@ +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::Object, +}; + +/// Frame includes cells which are on the edges of each side. +/// Therefore it's [`Object`] implementation returns a subset of cells which are present in frame. +#[derive(Debug)] +pub struct Frame; + +impl<I> Object<I> for Frame +where + I: Records + ExactRecords, +{ + type Iter = FrameIter; + + fn cells(&self, records: &I) -> Self::Iter { + FrameIter::new(records.count_rows(), records.count_columns()) + } +} + +/// An [`Iterator`] which goes goes over all cell on a frame of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct FrameIter { + rows: usize, + cols: usize, + row: usize, + col: usize, +} + +impl FrameIter { + const fn new(count_rows: usize, count_columns: usize) -> Self { + Self { + rows: count_rows, + cols: count_columns, + row: 0, + col: 0, + } + } +} + +impl Iterator for FrameIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if self.cols == 0 || self.rows == 0 { + return None; + } + + if self.row == self.rows { + return None; + } + + let row = self.row; + let col = self.col; + + self.col += 1; + + if self.col == self.cols { + self.row += 1; + self.col = 0; + } + + Some(Entity::Cell(row, col)) + } +} diff --git a/vendor/tabled/src/settings/object/mod.rs b/vendor/tabled/src/settings/object/mod.rs new file mode 100644 index 000000000..46893c71d --- /dev/null +++ b/vendor/tabled/src/settings/object/mod.rs @@ -0,0 +1,765 @@ +//! This module contains a list of primitives that implement a [`Object`] trait. +//! They help to locate a necessary segment on a [`Table`]. +//! +//! [`Table`]: crate::Table + +mod cell; +mod columns; +mod frame; +mod rows; +mod segment; +pub(crate) mod util; + +use std::{collections::HashSet, marker::PhantomData}; + +use self::segment::SectorCellsIter; + +use crate::{ + grid::config::{Entity, EntityIterator}, + grid::records::{ExactRecords, Records}, +}; + +pub use cell::{Cell, EntityOnce}; +pub use columns::{Column, Columns, ColumnsIter, FirstColumn, LastColumn, LastColumnOffset}; +pub use frame::{Frame, FrameIter}; +pub use rows::{FirstRow, LastRow, LastRowOffset, Row, Rows, RowsIter}; +pub use segment::{SectorIter, Segment, SegmentAll}; + +/// Object helps to locate a necessary part of a [`Table`]. +/// +/// [`Table`]: crate::Table +pub trait Object<R> { + /// An [`Iterator`] which returns a list of cells. + type Iter: Iterator<Item = Entity>; + + /// Cells returns a set of coordinates of cells. + fn cells(&self, records: &R) -> Self::Iter; + + /// Combines cells. + /// It doesn't repeat cells. + fn and<O>(self, rhs: O) -> UnionCombination<Self, O, R> + where + Self: Sized, + { + UnionCombination::new(self, rhs) + } + + /// Excludes rhs cells from this cells. + fn not<O>(self, rhs: O) -> DiffCombination<Self, O, R> + where + Self: Sized, + { + DiffCombination::new(self, rhs) + } + + /// Returns cells which are present in both [`Object`]s only. + fn intersect<O>(self, rhs: O) -> IntersectionCombination<Self, O, R> + where + Self: Sized, + { + IntersectionCombination::new(self, rhs) + } + + /// Returns cells which are not present in target [`Object`]. + fn inverse(self) -> InversionCombination<Self, R> + where + Self: Sized, + { + InversionCombination::new(self) + } +} + +/// Combination struct used for chaining [`Object`]'s. +/// +/// Combines 2 sets of cells into one. +/// +/// Duplicates are removed from the output set. +#[derive(Debug)] +pub struct UnionCombination<L, R, I> { + lhs: L, + rhs: R, + _records: PhantomData<I>, +} + +impl<L, R, I> UnionCombination<L, R, I> { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl<I, L, R> Object<I> for UnionCombination<L, R, I> +where + L: Object<I>, + R: Object<I>, + I: Records + ExactRecords, +{ + type Iter = UnionIter<L::Iter, R::Iter>; + + fn cells(&self, records: &I) -> Self::Iter { + let lhs = self.lhs.cells(records); + let rhs = self.rhs.cells(records); + + UnionIter::new(lhs, rhs, records.count_rows(), records.count_columns()) + } +} + +/// Difference struct used for chaining [`Object`]'s. +/// +/// Returns cells from 1st set with removed ones from the 2nd set. +#[derive(Debug)] +pub struct DiffCombination<L, R, I> { + lhs: L, + rhs: R, + _records: PhantomData<I>, +} + +impl<L, R, I> DiffCombination<L, R, I> { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl<I, L, R> Object<I> for DiffCombination<L, R, I> +where + L: Object<I>, + R: Object<I>, + I: Records + ExactRecords, +{ + type Iter = DiffIter<L::Iter>; + + fn cells(&self, records: &I) -> Self::Iter { + let lhs = self.lhs.cells(records); + let rhs = self.rhs.cells(records); + + DiffIter::new(lhs, rhs, records.count_rows(), records.count_columns()) + } +} + +/// Intersection struct used for chaining [`Object`]'s. +/// +/// Returns cells which are present in 2 sets. +/// But not in one of them +#[derive(Debug)] +pub struct IntersectionCombination<L, R, I> { + lhs: L, + rhs: R, + _records: PhantomData<I>, +} + +impl<L, R, I> IntersectionCombination<L, R, I> { + fn new(lhs: L, rhs: R) -> Self { + Self { + lhs, + rhs, + _records: PhantomData, + } + } +} + +impl<I, L, R> Object<I> for IntersectionCombination<L, R, I> +where + L: Object<I>, + R: Object<I>, + I: Records + ExactRecords, +{ + type Iter = IntersectIter<L::Iter>; + + fn cells(&self, records: &I) -> Self::Iter { + let lhs = self.lhs.cells(records); + let rhs = self.rhs.cells(records); + + IntersectIter::new(lhs, rhs, records.count_rows(), records.count_columns()) + } +} + +/// Inversion struct used for chaining [`Object`]'s. +/// +/// Returns cells which are present in 2 sets. +/// But not in one of them +#[derive(Debug)] +pub struct InversionCombination<O, I> { + obj: O, + _records: PhantomData<I>, +} + +impl<O, I> InversionCombination<O, I> { + fn new(obj: O) -> Self { + Self { + obj, + _records: PhantomData, + } + } +} + +impl<I, O> Object<I> for InversionCombination<O, I> +where + O: Object<I>, + I: Records + ExactRecords, +{ + type Iter = InversionIter; + + fn cells(&self, records: &I) -> Self::Iter { + let obj = self.obj.cells(records); + + InversionIter::new(obj, records.count_rows(), records.count_columns()) + } +} + +/// An [`Iterator`] which goes over a combination [`Object::Iter`]. +#[derive(Debug)] +pub struct UnionIter<L, R> { + lhs: Option<L>, + rhs: R, + seen: HashSet<(usize, usize)>, + current: Option<EntityIterator>, + count_rows: usize, + count_cols: usize, +} + +impl<L, R> UnionIter<L, R> +where + L: Iterator<Item = Entity>, + R: Iterator<Item = Entity>, +{ + fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self { + let size = match lhs.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + Self { + lhs: Some(lhs), + rhs, + seen: HashSet::with_capacity(size), + current: None, + count_rows, + count_cols, + } + } +} + +impl<L, R> Iterator for UnionIter<L, R> +where + L: Iterator<Item = Entity>, + R: Iterator<Item = Entity>, +{ + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(iter) = self.current.as_mut() { + for p in iter.by_ref() { + if self.lhs.is_none() && self.seen.contains(&p) { + continue; + } + + let _ = self.seen.insert(p); + return Some(Entity::Cell(p.0, p.1)); + } + } + + if let Some(lhs) = self.lhs.as_mut() { + for entity in lhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + if let Some(p) = iter.by_ref().next() { + let _ = self.seen.insert(p); + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + + self.lhs = None; + } + + for entity in self.rhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + + for p in iter.by_ref() { + if !self.seen.contains(&p) { + let _ = self.seen.insert(p); + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + None + } +} + +/// An [`Iterator`] which goes over only cells which are present in first [`Object::Iter`] but not second. +#[derive(Debug)] +pub struct DiffIter<L> { + lhs: L, + seen: HashSet<(usize, usize)>, + count_rows: usize, + count_cols: usize, + current: Option<EntityIterator>, +} + +impl<L> DiffIter<L> +where + L: Iterator<Item = Entity>, +{ + fn new<R>(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self + where + R: Iterator<Item = Entity>, + { + let size = match rhs.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + let mut seen = HashSet::with_capacity(size); + for entity in rhs { + seen.extend(entity.iter(count_rows, count_cols)); + } + + Self { + lhs, + seen, + count_rows, + count_cols, + current: None, + } + } +} + +impl<L> Iterator for DiffIter<L> +where + L: Iterator<Item = Entity>, +{ + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(iter) = self.current.as_mut() { + for p in iter.by_ref() { + if !self.seen.contains(&p) { + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + for entity in self.lhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + + for p in iter.by_ref() { + if !self.seen.contains(&p) { + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + None + } +} + +/// An [`Iterator`] which goes goes over cells which are present in both [`Object::Iter`]ators. +#[derive(Debug)] +pub struct IntersectIter<L> { + lhs: L, + seen: HashSet<(usize, usize)>, + count_rows: usize, + count_cols: usize, + current: Option<EntityIterator>, +} + +impl<L> IntersectIter<L> +where + L: Iterator<Item = Entity>, +{ + fn new<R>(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self + where + R: Iterator<Item = Entity>, + { + let size = match rhs.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + let mut seen = HashSet::with_capacity(size); + for entity in rhs { + seen.extend(entity.iter(count_rows, count_cols)); + } + + Self { + lhs, + seen, + count_rows, + count_cols, + current: None, + } + } +} + +impl<L> Iterator for IntersectIter<L> +where + L: Iterator<Item = Entity>, +{ + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(iter) = self.current.as_mut() { + for p in iter.by_ref() { + if self.seen.contains(&p) { + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + for entity in self.lhs.by_ref() { + let mut iter = entity.iter(self.count_rows, self.count_cols); + + for p in iter.by_ref() { + if self.seen.contains(&p) { + self.current = Some(iter); + return Some(Entity::Cell(p.0, p.1)); + } + } + } + + None + } +} + +/// An [`Iterator`] which goes goes over cells which are not present an [`Object::Iter`]ator. +#[derive(Debug)] +pub struct InversionIter { + all: SectorCellsIter, + seen: HashSet<(usize, usize)>, +} + +impl InversionIter { + fn new<O>(obj: O, count_rows: usize, count_columns: usize) -> Self + where + O: Iterator<Item = Entity>, + { + let size = match obj.size_hint() { + (s1, Some(s2)) if s1 == s2 => s1, + _ => 0, + }; + + let mut seen = HashSet::with_capacity(size); + for entity in obj { + seen.extend(entity.iter(count_rows, count_columns)); + } + + let all = SectorCellsIter::new(0, count_rows, 0, count_columns); + + Self { all, seen } + } +} + +impl Iterator for InversionIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + for p in self.all.by_ref() { + if !self.seen.contains(&p) { + return Some(Entity::Cell(p.0, p.1)); + } + } + + None + } +} + +#[cfg(test)] +mod tests { + use crate::grid::records::vec_records::VecRecords; + + use super::*; + + #[test] + fn cell_test() { + assert_eq!(vec_cells((0, 0), 2, 3), [Entity::Cell(0, 0)]); + assert_eq!(vec_cells((1, 1), 2, 3), [Entity::Cell(1, 1)]); + assert_eq!(vec_cells((1, 1), 0, 0), [Entity::Cell(1, 1)]); + assert_eq!(vec_cells((1, 100), 2, 3), [Entity::Cell(1, 100)]); + assert_eq!(vec_cells((100, 1), 2, 3), [Entity::Cell(100, 1)]); + } + + #[test] + fn columns_test() { + assert_eq!( + vec_cells(Columns::new(..), 2, 3), + [Entity::Column(0), Entity::Column(1), Entity::Column(2)] + ); + assert_eq!( + vec_cells(Columns::new(1..), 2, 3), + [Entity::Column(1), Entity::Column(2)] + ); + assert_eq!(vec_cells(Columns::new(2..), 2, 3), [Entity::Column(2)]); + assert_eq!(vec_cells(Columns::new(3..), 2, 3), []); + assert_eq!(vec_cells(Columns::new(3..), 0, 0), []); + assert_eq!(vec_cells(Columns::new(0..1), 2, 3), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::new(1..2), 2, 3), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::new(2..3), 2, 3), [Entity::Column(2)]); + assert_eq!(vec_cells(Columns::new(..), 0, 0), []); + assert_eq!(vec_cells(Columns::new(..), 2, 0), []); + assert_eq!(vec_cells(Columns::new(..), 0, 3), []); + } + + #[test] + fn first_column_test() { + assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::first(), 0, 0), []); + assert_eq!(vec_cells(Columns::first(), 10, 0), []); + assert_eq!(vec_cells(Columns::first(), 0, 10), []); + } + + #[test] + fn last_column_test() { + assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::last(), 5, 29), [Entity::Column(28)]); + assert_eq!(vec_cells(Columns::last(), 0, 0), []); + assert_eq!(vec_cells(Columns::last(), 10, 0), []); + assert_eq!(vec_cells(Columns::last(), 0, 10), []); + } + + #[test] + fn last_column_sub_test() { + assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::last() - 0, 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::last() - 1, 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::last() - 2, 5, 2), []); + assert_eq!(vec_cells(Columns::last() - 100, 5, 2), []); + } + + #[test] + fn first_column_add_test() { + assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::first() + 0, 5, 2), [Entity::Column(0)]); + assert_eq!(vec_cells(Columns::first() + 1, 5, 2), [Entity::Column(1)]); + assert_eq!(vec_cells(Columns::first() + 2, 5, 2), [Entity::Column(2)]); + assert_eq!( + vec_cells(Columns::first() + 100, 5, 2), + [Entity::Column(100)] + ); + } + + #[test] + fn rows_test() { + assert_eq!( + vec_cells(Rows::new(..), 2, 3), + [Entity::Row(0), Entity::Row(1)] + ); + assert_eq!(vec_cells(Rows::new(1..), 2, 3), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::new(2..), 2, 3), []); + assert_eq!(vec_cells(Rows::new(2..), 0, 0), []); + assert_eq!(vec_cells(Rows::new(0..1), 2, 3), [Entity::Row(0)],); + assert_eq!(vec_cells(Rows::new(1..2), 2, 3), [Entity::Row(1)],); + assert_eq!(vec_cells(Rows::new(..), 0, 0), []); + assert_eq!(vec_cells(Rows::new(..), 0, 3), []); + assert_eq!( + vec_cells(Rows::new(..), 2, 0), + [Entity::Row(0), Entity::Row(1)] + ); + } + + #[test] + fn last_row_test() { + assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::last(), 100, 2), [Entity::Row(99)]); + assert_eq!(vec_cells(Rows::last(), 0, 0), []); + assert_eq!(vec_cells(Rows::last(), 5, 0), []); + assert_eq!(vec_cells(Rows::last(), 0, 2), []); + } + + #[test] + fn first_row_test() { + assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first(), 100, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first(), 0, 0), []); + assert_eq!(vec_cells(Rows::first(), 5, 0), []); + assert_eq!(vec_cells(Rows::first(), 0, 2), []); + } + + #[test] + fn last_row_sub_test() { + assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::last() - 0, 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::last() - 1, 5, 2), [Entity::Row(3)]); + assert_eq!(vec_cells(Rows::last() - 2, 5, 2), [Entity::Row(2)]); + assert_eq!(vec_cells(Rows::last() - 3, 5, 2), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::last() - 4, 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::last() - 5, 5, 2), []); + assert_eq!(vec_cells(Rows::last() - 100, 5, 2), []); + assert_eq!(vec_cells(Rows::last() - 1, 0, 0), []); + assert_eq!(vec_cells(Rows::last() - 1, 5, 0), []); + assert_eq!(vec_cells(Rows::last() - 1, 0, 2), []); + } + + #[test] + fn first_row_add_test() { + assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first() + 0, 5, 2), [Entity::Row(0)]); + assert_eq!(vec_cells(Rows::first() + 1, 5, 2), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::first() + 2, 5, 2), [Entity::Row(2)]); + assert_eq!(vec_cells(Rows::first() + 3, 5, 2), [Entity::Row(3)]); + assert_eq!(vec_cells(Rows::first() + 4, 5, 2), [Entity::Row(4)]); + assert_eq!(vec_cells(Rows::first() + 5, 5, 2), [Entity::Row(5)]); + assert_eq!(vec_cells(Rows::first() + 100, 5, 2), [Entity::Row(100)]); + assert_eq!(vec_cells(Rows::first() + 1, 0, 0), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::first() + 1, 5, 0), [Entity::Row(1)]); + assert_eq!(vec_cells(Rows::first() + 1, 0, 2), [Entity::Row(1)]); + } + + #[test] + fn frame_test() { + assert_eq!( + vec_cells(Frame, 2, 3), + [ + Entity::Cell(0, 0), + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 0), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!(vec_cells(Frame, 0, 0), []); + assert_eq!(vec_cells(Frame, 2, 0), []); + assert_eq!(vec_cells(Frame, 0, 2), []); + } + + #[test] + fn segment_test() { + assert_eq!( + vec_cells(Segment::new(.., ..), 2, 3), + [ + Entity::Cell(0, 0), + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 0), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!( + vec_cells(Segment::new(1.., ..), 2, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Segment::new(2.., ..), 2, 3), []); + + assert_eq!( + vec_cells(Segment::new(.., 1..), 2, 3), + [ + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!( + vec_cells(Segment::new(.., 2..), 2, 3), + [Entity::Cell(0, 2), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Segment::new(.., 3..), 2, 3), []); + + assert_eq!( + vec_cells(Segment::new(1.., 1..), 2, 3), + [Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!( + vec_cells(Segment::new(1..2, 1..2), 2, 3), + [Entity::Cell(1, 1)] + ); + + assert_eq!(vec_cells(Segment::new(5.., 5..), 2, 3), []); + } + + #[test] + fn object_and_test() { + assert_eq!( + vec_cells(Cell::new(0, 0).and(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 0)] + ); + assert_eq!( + vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 2, 3), + [Entity::Cell(0, 0), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 0, 0), []); + } + + #[test] + fn object_not_test() { + assert_eq!(vec_cells(Rows::first().not(Cell::new(0, 0)), 0, 0), []); + assert_eq!(vec_cells(Cell::new(0, 0).not(Cell::new(0, 0)), 2, 3), []); + assert_eq!( + vec_cells(Rows::first().not(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 1), Entity::Cell(0, 2)] + ); + assert_eq!( + vec_cells(Columns::single(1).not(Rows::single(1)), 3, 3), + [Entity::Cell(0, 1), Entity::Cell(2, 1)] + ); + assert_eq!( + vec_cells(Rows::single(1).not(Columns::single(1)), 3, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 2)] + ); + } + + #[test] + fn object_intersect_test() { + assert_eq!( + vec_cells(Rows::first().intersect(Cell::new(0, 0)), 0, 0), + [] + ); + assert_eq!( + vec_cells(Segment::all().intersect(Rows::single(1)), 2, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!( + vec_cells(Cell::new(0, 0).intersect(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 0)] + ); + assert_eq!( + vec_cells(Rows::first().intersect(Cell::new(0, 0)), 2, 3), + [Entity::Cell(0, 0)] + ); + // maybe we somehow shall not limit the rows/columns by the max count? + assert_eq!( + vec_cells(Rows::single(1).intersect(Columns::single(1)), 2, 1), + [] + ); + } + + #[test] + fn object_inverse_test() { + assert_eq!(vec_cells(Segment::all().inverse(), 2, 3), []); + assert_eq!( + vec_cells(Cell::new(0, 0).inverse(), 2, 3), + [ + Entity::Cell(0, 1), + Entity::Cell(0, 2), + Entity::Cell(1, 0), + Entity::Cell(1, 1), + Entity::Cell(1, 2) + ] + ); + assert_eq!( + vec_cells(Rows::first().inverse(), 2, 3), + [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] + ); + assert_eq!(vec_cells(Rows::first().inverse(), 0, 0), []); + } + + fn vec_cells<O: Object<VecRecords<String>>>( + o: O, + count_rows: usize, + count_cols: usize, + ) -> Vec<Entity> { + let data = vec![vec![String::default(); count_cols]; count_rows]; + let records = VecRecords::new(data); + o.cells(&records).collect::<Vec<_>>() + } +} diff --git a/vendor/tabled/src/settings/object/rows.rs b/vendor/tabled/src/settings/object/rows.rs new file mode 100644 index 000000000..b32e79ad5 --- /dev/null +++ b/vendor/tabled/src/settings/object/rows.rs @@ -0,0 +1,213 @@ +use std::ops::{Add, RangeBounds, Sub}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::{cell::EntityOnce, Object}, +}; + +use super::util::bounds_to_usize; + +/// Row denotes a set of cells on given rows on a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Rows<R> { + range: R, +} + +impl<R> Rows<R> { + /// Returns a new instance of [`Rows`] for a range of rows. + /// + /// If the boundaries are exceeded it may panic. + pub fn new(range: R) -> Self + where + R: RangeBounds<usize>, + { + Self { range } + } + + pub(crate) const fn get_range(&self) -> &R { + &self.range + } +} + +impl Rows<()> { + /// Returns a new instance of [`Rows`] with a single row. + /// + /// If the boundaries are exceeded it may panic. + pub const fn single(index: usize) -> Row { + Row { index } + } + + /// Returns a first row [`Object`]. + /// + /// If the table has 0 rows returns an empty set of cells. + pub const fn first() -> FirstRow { + FirstRow + } + + /// Returns a last row [`Object`]. + /// + /// If the table has 0 rows returns an empty set of cells. + pub const fn last() -> LastRow { + LastRow + } +} + +impl<I, R> Object<I> for Rows<R> +where + R: RangeBounds<usize>, + I: ExactRecords, +{ + type Iter = RowsIter; + + fn cells(&self, records: &I) -> Self::Iter { + let start = self.range.start_bound(); + let end = self.range.end_bound(); + let max = records.count_rows(); + let (x, y) = bounds_to_usize(start, end, max); + + RowsIter::new(x, y) + } +} + +/// A row which is located by an offset from the first row. +#[derive(Debug, Clone, Copy)] +pub struct Row { + index: usize, +} + +impl<I> Object<I> for Row { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Row(self.index))) + } +} + +impl From<Row> for usize { + fn from(val: Row) -> Self { + val.index + } +} + +/// This structure represents the first row of a [`Table`]. +/// It's often contains headers data. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct FirstRow; + +impl<I> Object<I> for FirstRow +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + if records.count_columns() == 0 || records.count_rows() == 0 { + return EntityOnce::new(None); + } + + EntityOnce::new(Some(Entity::Row(0))) + } +} + +impl Add<usize> for FirstRow { + type Output = Row; + + fn add(self, rhs: usize) -> Self::Output { + Row { index: rhs } + } +} + +/// This structure represents the last row of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct LastRow; + +impl<I> Object<I> for LastRow +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + let count_rows = records.count_rows(); + if records.count_columns() == 0 || count_rows == 0 { + return EntityOnce::new(None); + } + + let row = if count_rows == 0 { 0 } else { count_rows - 1 }; + + EntityOnce::new(Some(Entity::Row(row))) + } +} + +impl Sub<usize> for LastRow { + type Output = LastRowOffset; + + fn sub(self, rhs: usize) -> Self::Output { + LastRowOffset { offset: rhs } + } +} + +/// A row which is located by an offset from the last row. +#[derive(Debug)] +pub struct LastRowOffset { + offset: usize, +} + +impl<I> Object<I> for LastRowOffset +where + I: Records + ExactRecords, +{ + type Iter = EntityOnce; + + fn cells(&self, records: &I) -> Self::Iter { + let count_rows = records.count_rows(); + if records.count_columns() == 0 || count_rows == 0 { + return EntityOnce::new(None); + } + + let row = if count_rows == 0 { 0 } else { count_rows - 1 }; + if self.offset > row { + return EntityOnce::new(None); + } + + let row = row - self.offset; + EntityOnce::new(Some(Entity::Row(row))) + } +} + +/// An [`Iterator`] which goes goes over all rows of a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct RowsIter { + start: usize, + end: usize, +} + +impl RowsIter { + const fn new(start: usize, end: usize) -> Self { + Self { start, end } + } +} + +impl Iterator for RowsIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + if self.start >= self.end { + return None; + } + + let col = self.start; + self.start += 1; + + Some(Entity::Row(col)) + } +} diff --git a/vendor/tabled/src/settings/object/segment.rs b/vendor/tabled/src/settings/object/segment.rs new file mode 100644 index 000000000..9b59ada47 --- /dev/null +++ b/vendor/tabled/src/settings/object/segment.rs @@ -0,0 +1,151 @@ +use std::ops::{RangeBounds, RangeFull}; + +use crate::{ + grid::config::Entity, + grid::records::{ExactRecords, Records}, + settings::object::{cell::EntityOnce, Object}, +}; + +use super::util::bounds_to_usize; + +/// This structure represents a sub table of [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Segment<C, R> { + columns: C, + rows: R, +} + +impl Segment<RangeFull, RangeFull> { + /// Returns a table segment on which are present all cells. + pub fn all() -> SegmentAll { + SegmentAll + } +} + +impl<C, R> Segment<C, R> +where + C: RangeBounds<usize>, + R: RangeBounds<usize>, +{ + /// This function builds a [`Segment`]. + pub fn new(rows: R, columns: C) -> Self { + Self { columns, rows } + } +} + +impl<I, C, R> Object<I> for Segment<C, R> +where + C: RangeBounds<usize>, + R: RangeBounds<usize>, + I: Records + ExactRecords, +{ + type Iter = SectorIter; + + fn cells(&self, records: &I) -> Self::Iter { + let start = self.rows.start_bound(); + let end = self.rows.end_bound(); + let max = records.count_rows(); + let (rows_start, rows_end) = bounds_to_usize(start, end, max); + + let start = self.columns.start_bound(); + let end = self.columns.end_bound(); + let max = records.count_columns(); + let (cols_start, cols_end) = bounds_to_usize(start, end, max); + + SectorIter::new(rows_start, rows_end, cols_start, cols_end) + } +} + +/// This is a segment which contains all cells on the table. +/// +/// Can be created from [`Segment::all`]. +#[derive(Debug)] +pub struct SegmentAll; + +impl<I> Object<I> for SegmentAll { + type Iter = EntityOnce; + + fn cells(&self, _: &I) -> Self::Iter { + EntityOnce::new(Some(Entity::Global)) + } +} + +/// An [`Iterator`] which goes goes over all cell in a sector in a [`Table`]. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct SectorIter { + iter: SectorCellsIter, +} + +impl SectorIter { + const fn new(rows_start: usize, rows_end: usize, cols_start: usize, cols_end: usize) -> Self { + Self { + iter: SectorCellsIter::new(rows_start, rows_end, cols_start, cols_end), + } + } +} + +impl Iterator for SectorIter { + type Item = Entity; + + fn next(&mut self) -> Option<Self::Item> { + let (row, col) = self.iter.next()?; + Some(Entity::Cell(row, col)) + } +} + +#[derive(Debug)] +pub(crate) struct SectorCellsIter { + rows_end: usize, + cols_start: usize, + cols_end: usize, + row: usize, + col: usize, +} + +impl SectorCellsIter { + /// Create an iterator from 1st row to last from 1st col to last. + pub(crate) const fn new( + rows_start: usize, + rows_end: usize, + cols_start: usize, + cols_end: usize, + ) -> Self { + Self { + rows_end, + cols_start, + cols_end, + row: rows_start, + col: cols_start, + } + } +} + +impl Iterator for SectorCellsIter { + type Item = (usize, usize); + + fn next(&mut self) -> Option<Self::Item> { + if self.row >= self.rows_end { + return None; + } + + if self.col >= self.cols_end { + return None; + } + + let row = self.row; + let col = self.col; + + self.col += 1; + + if self.col == self.cols_end { + self.row += 1; + self.col = self.cols_start; + } + + Some((row, col)) + } +} diff --git a/vendor/tabled/src/settings/object/util.rs b/vendor/tabled/src/settings/object/util.rs new file mode 100644 index 000000000..94ca20e86 --- /dev/null +++ b/vendor/tabled/src/settings/object/util.rs @@ -0,0 +1,22 @@ +use std::ops::Bound; + +/// Converts a range bound to its indexes. +pub(super) fn bounds_to_usize( + left: Bound<&usize>, + right: Bound<&usize>, + count_elements: usize, +) -> (usize, usize) { + match (left, right) { + (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), + (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), + (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), + (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), + (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), + (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), + (Bound::Excluded(_), Bound::Unbounded) + | (Bound::Excluded(_), Bound::Included(_)) + | (Bound::Excluded(_), Bound::Excluded(_)) => { + unreachable!("A start bound can't be excluded") + } + } +} diff --git a/vendor/tabled/src/settings/padding/mod.rs b/vendor/tabled/src/settings/padding/mod.rs new file mode 100644 index 000000000..af343b3cb --- /dev/null +++ b/vendor/tabled/src/settings/padding/mod.rs @@ -0,0 +1,164 @@ +//! This module contains a [`Padding`] setting of a cell on a [`Table`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Padding, Style, Modify, object::Cell}}; +//! +//! let table = Table::new("2022".chars()) +//! .with(Style::modern()) +//! .with(Modify::new((2, 0)).with(Padding::new(1, 1, 2, 2))) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌──────┐\n", +//! "│ char │\n", +//! "├──────┤\n", +//! "│ 2 │\n", +//! "├──────┤\n", +//! "│ │\n", +//! "│ │\n", +//! "│ 0 │\n", +//! "│ │\n", +//! "│ │\n", +//! "├──────┤\n", +//! "│ 2 │\n", +//! "├──────┤\n", +//! "│ 2 │\n", +//! "└──────┘", +//! ), +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::{ + color::StaticColor, + config::{CompactConfig, CompactMultilineConfig}, + config::{Indent, Sides}, + }, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::{color::AnsiColor, config::ColoredConfig, config::Entity}; +#[cfg(feature = "std")] +use crate::settings::CellOption; + +/// Padding is responsible for a left/right/top/bottom inner indent of a particular cell. +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// # use tabled::{settings::{Style, Padding, object::Rows, Modify}, Table}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data).with(Modify::new(Rows::single(0)).with(Padding::new(0, 0, 1, 1).fill('>', '<', '^', 'V'))); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Padding<C = StaticColor> { + indent: Sides<Indent>, + colors: Option<Sides<C>>, +} + +impl Padding { + /// Construct's an Padding object. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Padding::fill`] function. + pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { + Self { + indent: Sides::new( + Indent::spaced(left), + Indent::spaced(right), + Indent::spaced(top), + Indent::spaced(bottom), + ), + colors: None, + } + } + + /// Construct's an Padding object with all sides set to 0. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Padding::fill`] function. + pub const fn zero() -> Self { + Self::new(0, 0, 0, 0) + } +} + +impl<Color> Padding<Color> { + /// The function, sets a characters for the padding on an each side. + pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self { + self.indent.left.fill = left; + self.indent.right.fill = right; + self.indent.top.fill = top; + self.indent.bottom.fill = bottom; + self + } + + /// The function, sets a characters for the padding on an each side. + pub fn colorize<C>(self, left: C, right: C, top: C, bottom: C) -> Padding<C> { + Padding { + indent: self.indent, + colors: Some(Sides::new(left, right, top, bottom)), + } + } +} + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, ColoredConfig> for Padding<C> +where + C: Into<AnsiColor<'static>> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let indent = self.indent; + let pad = Sides::new(indent.left, indent.right, indent.top, indent.bottom); + cfg.set_padding(entity, pad); + + if let Some(colors) = &self.colors { + let pad = Sides::new( + Some(colors.left.clone().into()), + Some(colors.right.clone().into()), + Some(colors.top.clone().into()), + Some(colors.bottom.clone().into()), + ); + cfg.set_padding_color(entity, pad); + } + } +} + +#[cfg(feature = "std")] +impl<R, D, C> TableOption<R, D, ColoredConfig> for Padding<C> +where + C: Into<AnsiColor<'static>> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + <Self as CellOption<R, ColoredConfig>>::change(self, records, cfg, Entity::Global) + } +} + +impl<R, D, C> TableOption<R, D, CompactConfig> for Padding<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + *cfg = cfg.set_padding(self.indent); + + if let Some(c) = self.colors { + let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); + *cfg = cfg.set_padding_color(colors); + } + } +} + +impl<R, D, C> TableOption<R, D, CompactMultilineConfig> for Padding<C> +where + C: Into<StaticColor> + Clone, +{ + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/panel/footer.rs b/vendor/tabled/src/settings/panel/footer.rs new file mode 100644 index 000000000..8d16481cf --- /dev/null +++ b/vendor/tabled/src/settings/panel/footer.rs @@ -0,0 +1,32 @@ +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +use super::Panel; + +/// Footer renders a [`Panel`] at the bottom. +/// See [`Panel`]. +#[derive(Debug)] +pub struct Footer<S>(S); + +impl<S> Footer<S> { + /// Creates a new object. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + Self(text) + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for Footer<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + Panel::horizontal(records.count_rows(), self.0.as_ref()).change(records, cfg, dimension); + } +} diff --git a/vendor/tabled/src/settings/panel/header.rs b/vendor/tabled/src/settings/panel/header.rs new file mode 100644 index 000000000..e9d398cb5 --- /dev/null +++ b/vendor/tabled/src/settings/panel/header.rs @@ -0,0 +1,32 @@ +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +use super::Panel; + +/// Header inserts a [`Panel`] at the top. +/// See [`Panel`]. +#[derive(Debug)] +pub struct Header<S>(S); + +impl<S> Header<S> { + /// Creates a new object. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + Self(text) + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for Header<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + Panel::horizontal(0, self.0.as_ref()).change(records, cfg, dimension); + } +} diff --git a/vendor/tabled/src/settings/panel/horizontal_panel.rs b/vendor/tabled/src/settings/panel/horizontal_panel.rs new file mode 100644 index 000000000..d5871d720 --- /dev/null +++ b/vendor/tabled/src/settings/panel/horizontal_panel.rs @@ -0,0 +1,80 @@ +use crate::{ + grid::config::{ColoredConfig, SpannedConfig}, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +/// A horizontal/column span from 0 to a count rows. +#[derive(Debug)] +pub struct HorizontalPanel<S> { + text: S, + row: usize, +} + +impl<S> HorizontalPanel<S> { + /// Creates a new horizontal panel. + pub fn new(row: usize, text: S) -> Self { + Self { row, text } + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for HorizontalPanel<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if self.row > count_rows { + return; + } + + let is_intersect_vertical_span = (0..records.count_columns()) + .any(|col| cfg.is_cell_covered_by_row_span((self.row, col))); + if is_intersect_vertical_span { + return; + } + + move_rows_aside(records, self.row); + move_row_spans(cfg, self.row); + + let text = self.text.as_ref().to_owned(); + records.set((self.row, 0), text); + + cfg.set_column_span((self.row, 0), count_cols); + } +} + +fn move_rows_aside<R: ExactRecords + Resizable>(records: &mut R, row: usize) { + records.push_row(); + + let count_rows = records.count_rows(); + + let shift_count = count_rows - row; + for i in 1..shift_count { + let row = count_rows - i; + records.swap_row(row, row - 1); + } +} + +fn move_row_spans(cfg: &mut SpannedConfig, target_row: usize) { + for ((row, col), span) in cfg.get_column_spans() { + if row < target_row { + continue; + } + + cfg.set_column_span((row, col), 1); + cfg.set_column_span((row + 1, col), span); + } + + for ((row, col), span) in cfg.get_row_spans() { + if row < target_row { + continue; + } + + cfg.set_row_span((row, col), 1); + cfg.set_row_span((row + 1, col), span); + } +} diff --git a/vendor/tabled/src/settings/panel/mod.rs b/vendor/tabled/src/settings/panel/mod.rs new file mode 100644 index 000000000..e4e819b6c --- /dev/null +++ b/vendor/tabled/src/settings/panel/mod.rs @@ -0,0 +1,127 @@ +//! This module contains primitives to create a spread row. +//! Ultimately it is a cell with a span set to a number of columns on the [`Table`]. +//! +//! You can use a [`Span`] to set a custom span. +//! +//! # Example +//! +//! ``` +//! use tabled::{Table, settings::Panel}; +//! +//! let data = [[1, 2, 3], [4, 5, 6]]; +//! +//! let table = Table::new(data) +//! .with(Panel::vertical(1, "S\np\nl\ni\nt")) +//! .with(Panel::header("Numbers")) +//! .to_string(); +//! +//! println!("{}", table); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+---+---+---+---+\n", +//! "| Numbers |\n", +//! "+---+---+---+---+\n", +//! "| 0 | S | 1 | 2 |\n", +//! "+---+ p +---+---+\n", +//! "| 1 | l | 2 | 3 |\n", +//! "+---+ i +---+---+\n", +//! "| 4 | t | 5 | 6 |\n", +//! "+---+---+---+---+", +//! ) +//! ) +//! ``` +//! +//! [`Table`]: crate::Table +//! [`Span`]: crate::settings::span::Span + +mod footer; +mod header; +mod horizontal_panel; +mod vertical_panel; + +pub use footer::Footer; +pub use header::Header; +pub use horizontal_panel::HorizontalPanel; +pub use vertical_panel::VerticalPanel; + +/// Panel allows to add a Row which has 1 continues Cell to a [`Table`]. +/// +/// See `examples/panel.rs`. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Panel; + +impl Panel { + /// Creates an empty vertical row at given index. + /// + /// ``` + /// use tabled::{settings::Panel, Table}; + /// + /// let data = [[1, 2, 3], [4, 5, 6]]; + /// + /// let table = Table::new(data) + /// .with(Panel::vertical(1, "Tabled Releases")) + /// .to_string(); + /// + /// println!("{}", table); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+---+-----------------+---+---+\n", + /// "| 0 | Tabled Releases | 1 | 2 |\n", + /// "+---+ +---+---+\n", + /// "| 1 | | 2 | 3 |\n", + /// "+---+ +---+---+\n", + /// "| 4 | | 5 | 6 |\n", + /// "+---+-----------------+---+---+", + /// ) + /// ) + /// ``` + pub fn vertical<S: AsRef<str>>(column: usize, text: S) -> VerticalPanel<S> { + VerticalPanel::new(column, text) + } + + /// Creates an empty horizontal row at given index. + /// + /// ``` + /// use tabled::{Table, settings::Panel}; + /// + /// let data = [[1, 2, 3], [4, 5, 6]]; + /// + /// let table = Table::new(data) + /// .with(Panel::vertical(1, "")) + /// .to_string(); + /// + /// println!("{}", table); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+---+--+---+---+\n", + /// "| 0 | | 1 | 2 |\n", + /// "+---+ +---+---+\n", + /// "| 1 | | 2 | 3 |\n", + /// "+---+ +---+---+\n", + /// "| 4 | | 5 | 6 |\n", + /// "+---+--+---+---+", + /// ) + /// ) + /// ``` + pub fn horizontal<S: AsRef<str>>(row: usize, text: S) -> HorizontalPanel<S> { + HorizontalPanel::new(row, text) + } + + /// Creates an horizontal row at first row. + pub fn header<S: AsRef<str>>(text: S) -> Header<S> { + Header::new(text) + } + + /// Creates an horizontal row at last row. + pub fn footer<S: AsRef<str>>(text: S) -> Footer<S> { + Footer::new(text) + } +} diff --git a/vendor/tabled/src/settings/panel/vertical_panel.rs b/vendor/tabled/src/settings/panel/vertical_panel.rs new file mode 100644 index 000000000..ddc0a562b --- /dev/null +++ b/vendor/tabled/src/settings/panel/vertical_panel.rs @@ -0,0 +1,83 @@ +use crate::{ + grid::config::{ColoredConfig, SpannedConfig}, + grid::records::{ExactRecords, Records, RecordsMut, Resizable}, + settings::TableOption, +}; + +/// A vertical/row span from 0 to a count columns. +#[derive(Debug)] +pub struct VerticalPanel<S> { + text: S, + col: usize, +} + +impl<S> VerticalPanel<S> { + /// Creates a new vertical panel. + pub fn new(col: usize, text: S) -> Self + where + S: AsRef<str>, + { + Self { text, col } + } +} + +impl<S, R, D> TableOption<R, D, ColoredConfig> for VerticalPanel<S> +where + S: AsRef<str>, + R: Records + ExactRecords + Resizable + RecordsMut<String>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + if self.col > count_cols { + return; + } + + let is_intersect_horizontal_span = (0..=records.count_rows()) + .any(|row| cfg.is_cell_covered_by_column_span((row, self.col))); + + if is_intersect_horizontal_span { + return; + } + + move_columns_aside(records, self.col); + move_column_spans(cfg, self.col); + + let text = self.text.as_ref().to_owned(); + records.set((0, self.col), text); + + cfg.set_row_span((0, self.col), count_rows); + } +} + +fn move_columns_aside<R: Records + Resizable>(records: &mut R, column: usize) { + records.push_column(); + + let count_columns = records.count_columns(); + let shift_count = count_columns - column; + for i in 1..shift_count { + let col = count_columns - i; + records.swap_column(col, col - 1); + } +} + +fn move_column_spans(cfg: &mut SpannedConfig, target_column: usize) { + for ((row, col), span) in cfg.get_column_spans() { + if col < target_column { + continue; + } + + cfg.set_column_span((row, col), 1); + cfg.set_column_span((row, col + 1), span); + } + + for ((row, col), span) in cfg.get_row_spans() { + if col < target_column { + continue; + } + + cfg.set_row_span((row, col), 1); + cfg.set_row_span((row, col + 1), span); + } +} diff --git a/vendor/tabled/src/settings/peaker/mod.rs b/vendor/tabled/src/settings/peaker/mod.rs new file mode 100644 index 000000000..a49796b02 --- /dev/null +++ b/vendor/tabled/src/settings/peaker/mod.rs @@ -0,0 +1,94 @@ +//! The module contains [`Peaker`] trait and its implementations to be used in [`Height`] and [`Width`]. +//! +//! [`Width`]: crate::settings::width::Width +//! [`Height`]: crate::settings::height::Height + +/// A strategy of width function. +/// It determines the order how the function is applied. +pub trait Peaker { + /// Creates a new instance. + fn create() -> Self; + /// This function returns a column index which will be changed. + /// Or `None` if no changes are necessary. + fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option<usize>; +} + +/// A Peaker which goes over column 1 by 1. +#[derive(Debug, Default, Clone)] +pub struct PriorityNone { + i: usize, +} + +impl Peaker for PriorityNone { + fn create() -> Self { + Self { i: 0 } + } + + fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> { + let mut i = self.i; + let mut count_empty = 0; + while widths[i] == 0 { + i += 1; + if i >= widths.len() { + i = 0; + } + + count_empty += 1; + if count_empty == widths.len() { + return None; + } + } + + let col = i; + + i += 1; + if i >= widths.len() { + i = 0; + } + + self.i = i; + + Some(col) + } +} + +/// A Peaker which goes over the biggest column first. +#[derive(Debug, Default, Clone)] +pub struct PriorityMax; + +impl Peaker for PriorityMax { + fn create() -> Self { + Self + } + + fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option<usize> { + let col = (0..widths.len()).max_by_key(|&i| widths[i]).unwrap(); + if widths[col] == 0 { + None + } else { + Some(col) + } + } +} + +/// A Peaker which goes over the smallest column first. +#[derive(Debug, Default, Clone)] +pub struct PriorityMin; + +impl Peaker for PriorityMin { + fn create() -> Self { + Self + } + + fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option<usize> { + let col = (0..widths.len()) + .filter(|&i| min_widths.is_empty() || widths[i] > min_widths[i]) + .min_by_key(|&i| widths[i]) + .unwrap(); + if widths[col] == 0 { + None + } else { + Some(col) + } + } +} diff --git a/vendor/tabled/src/settings/rotate/mod.rs b/vendor/tabled/src/settings/rotate/mod.rs new file mode 100644 index 000000000..426417190 --- /dev/null +++ b/vendor/tabled/src/settings/rotate/mod.rs @@ -0,0 +1,155 @@ +//! This module contains a [`Rotate`] primitive which can be used in order to rotate [`Table`]. +//! +//! It's also possible to transpose the table at the point of construction. +//! See [`Builder::index`]. +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::Rotate}; +//! +//! let data = [[1, 2, 3], [4, 5, 6]]; +//! +//! let table = Table::new(data).with(Rotate::Left).to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+---+---+---+\n", +//! "| 2 | 3 | 6 |\n", +//! "+---+---+---+\n", +//! "| 1 | 2 | 5 |\n", +//! "+---+---+---+\n", +//! "| 0 | 1 | 4 |\n", +//! "+---+---+---+", +//! ) +//! ); +//! ``` +//! +//! [`Table`]: crate::Table +//! [`Builder::index`]: crate::builder::Builder::index + +// use core::cmp::max; +use core::cmp::max; + +use crate::{ + grid::records::{ExactRecords, Records, Resizable}, + settings::TableOption, +}; + +/// Rotate can be used to rotate a table by 90 degrees. +#[derive(Debug)] +pub enum Rotate { + /// Rotate [`Table`] to the left. + /// + /// [`Table`]: crate::Table + Left, + /// Rotate [`Table`] to the right. + /// + /// [`Table`]: crate::Table + Right, + /// Rotate [`Table`] to the top. + /// + /// So the top becomes the bottom. + /// + /// [`Table`]: crate::Table + Top, + /// Rotate [`Table`] to the bottom. + /// + /// So the top becomes the bottom. + /// + /// [`Table`]: crate::Table + Bottom, +} + +impl<R, D, C> TableOption<R, D, C> for Rotate +where + R: Records + ExactRecords + Resizable, +{ + fn change(self, records: &mut R, _: &mut C, _: &mut D) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + match self { + Self::Left => { + let size = max(count_rows, count_cols); + + { + for _ in count_rows..size { + records.push_row(); + } + + for _ in count_cols..size { + records.push_column(); + } + } + + for col in 0..size { + for row in col..size { + records.swap((col, row), (row, col)); + } + } + + for row in 0..count_cols / 2 { + records.swap_row(row, count_cols - row - 1); + } + + { + for (shift, row) in (count_rows..size).enumerate() { + let row = row - shift; + records.remove_column(row); + } + + for (shift, col) in (count_cols..size).enumerate() { + let col = col - shift; + records.remove_row(col); + } + } + } + Self::Right => { + let size = max(count_rows, count_cols); + + { + for _ in count_rows..size { + records.push_row(); + } + + for _ in count_cols..size { + records.push_column(); + } + } + + for col in 0..size { + for row in col..size { + records.swap((col, row), (row, col)); + } + } + + for col in 0..count_rows / 2 { + records.swap_column(col, count_rows - col - 1); + } + + { + for (shift, row) in (count_rows..size).enumerate() { + let row = row - shift; + records.remove_column(row); + } + + for (shift, col) in (count_cols..size).enumerate() { + let col = col - shift; + records.remove_row(col); + } + } + } + Self::Bottom | Self::Top => { + for row in 0..count_rows / 2 { + for col in 0..count_cols { + let last_row = count_rows - row - 1; + records.swap((last_row, col), (row, col)); + } + } + } + } + } +} diff --git a/vendor/tabled/src/settings/settings_list.rs b/vendor/tabled/src/settings/settings_list.rs new file mode 100644 index 000000000..dc8aec179 --- /dev/null +++ b/vendor/tabled/src/settings/settings_list.rs @@ -0,0 +1,71 @@ +use crate::settings::TableOption; + +#[cfg(feature = "std")] +use crate::grid::config::Entity; +#[cfg(feature = "std")] +use crate::settings::CellOption; + +/// Settings is a combinator of [`TableOption`]s. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Settings<A = EmptySettings, B = EmptySettings>(A, B); + +impl Default for Settings<EmptySettings, EmptySettings> { + fn default() -> Self { + Self(EmptySettings, EmptySettings) + } +} + +impl Settings<(), ()> { + /// Creates an empty list. + pub const fn empty() -> Settings<EmptySettings, EmptySettings> { + Settings(EmptySettings, EmptySettings) + } +} + +impl<A, B> Settings<A, B> { + /// Creates a new combinator. + pub const fn new(settings1: A, settings2: B) -> Settings<A, B> { + Settings(settings1, settings2) + } + + /// Add an option to a combinator. + pub const fn with<C>(self, settings: C) -> Settings<Self, C> { + Settings(self, settings) + } +} + +#[cfg(feature = "std")] +impl<R, C, A, B> CellOption<R, C> for Settings<A, B> +where + A: CellOption<R, C>, + B: CellOption<R, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { + self.0.change(records, cfg, entity); + self.1.change(records, cfg, entity); + } +} + +impl<R, D, C, A, B> TableOption<R, D, C> for Settings<A, B> +where + A: TableOption<R, D, C>, + B: TableOption<R, D, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, dims: &mut D) { + self.0.change(records, cfg, dims); + self.1.change(records, cfg, dims); + } +} + +/// A marker structure to be able to create an empty [`Settings`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EmptySettings; + +#[cfg(feature = "std")] +impl<R, C> CellOption<R, C> for EmptySettings { + fn change(self, _: &mut R, _: &mut C, _: Entity) {} +} + +impl<R, D, C> TableOption<R, D, C> for EmptySettings { + fn change(self, _: &mut R, _: &mut C, _: &mut D) {} +} diff --git a/vendor/tabled/src/settings/shadow/mod.rs b/vendor/tabled/src/settings/shadow/mod.rs new file mode 100644 index 000000000..6b8ff4861 --- /dev/null +++ b/vendor/tabled/src/settings/shadow/mod.rs @@ -0,0 +1,195 @@ +//! This module contains a [`Shadow`] option for a [`Table`]. +//! +//! # Example +//! +//! ``` +//! use tabled::{Table, settings::{Shadow, Style}}; +//! +//! let data = vec!["Hello", "World", "!"]; +//! +//! let table = Table::new(data) +//! .with(Style::markdown()) +//! .with(Shadow::new(1)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "| &str | \n", +//! "|-------|▒\n", +//! "| Hello |▒\n", +//! "| World |▒\n", +//! "| ! |▒\n", +//! " ▒▒▒▒▒▒▒▒▒", +//! ) +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use crate::{ + grid::color::AnsiColor, + grid::config::{ColoredConfig, Indent, Offset, Sides}, + settings::{color::Color, TableOption}, +}; + +/// The structure represents a shadow of a table. +/// +/// NOTICE: It uses [`Margin`] therefore it often can't be combined. +/// +/// [`Margin`]: crate::settings::Margin +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Shadow { + c: char, + size: usize, + size_offset: usize, + direction: Sides<bool>, + color: Option<Color>, +} + +impl Shadow { + /// A default fill character to be used. + pub const DEFAULT_FILL: char = '▒'; + + /// Construct's an [`Shadow`] object with default fill [`Shadow::DEFAULT_FILL`]. + /// + /// It uses space(' ') as a default fill character. + /// To set a custom character you can use [`Self::set_fill`] function. + pub fn new(size: usize) -> Self { + Self { + c: Self::DEFAULT_FILL, + size, + size_offset: 1, + direction: Sides::new(false, true, false, true), + color: None, + } + } + + /// The function, sets a characters for the [`Shadow`] to be used. + pub fn set_fill(mut self, c: char) -> Self { + self.c = c; + self + } + + /// Set an offset value (default is '1'). + pub fn set_offset(mut self, size: usize) -> Self { + self.size_offset = size; + self + } + + /// Switch shadow to top. + pub fn set_top(mut self) -> Self { + self.direction.top = true; + self.direction.bottom = false; + self + } + + /// Switch shadow to bottom. + pub fn set_bottom(mut self) -> Self { + self.direction.bottom = true; + self.direction.top = false; + self + } + + /// Switch shadow to left. + pub fn set_left(mut self) -> Self { + self.direction.left = true; + self.direction.right = false; + self + } + + /// Switch shadow to right. + pub fn set_right(mut self) -> Self { + self.direction.right = true; + self.direction.left = false; + self + } + + /// Sets a color for a shadow. + pub fn set_color(mut self, color: Color) -> Self { + self.color = Some(color); + self + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Shadow { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + set_margin(cfg, self.size, self.c, &self.direction); + set_margin_offset(cfg, self.size_offset, &self.direction); + + if let Some(color) = &self.color { + set_margin_color(cfg, color.clone().into(), &self.direction); + } + } +} + +fn set_margin(cfg: &mut ColoredConfig, size: usize, c: char, direction: &Sides<bool>) { + let mut margin: Sides<Indent> = Sides::default(); + if direction.top { + margin.top.size = size; + margin.top.fill = c; + } + + if direction.bottom { + margin.bottom.size = size; + margin.bottom.fill = c; + } + + if direction.left { + margin.left.size = size; + margin.left.fill = c; + } + + if direction.right { + margin.right.size = size; + margin.right.fill = c; + } + + cfg.set_margin(margin); +} + +fn set_margin_offset(cfg: &mut ColoredConfig, size: usize, direction: &Sides<bool>) { + let mut margin = Sides::filled(Offset::Begin(0)); + if direction.right && direction.bottom { + margin.bottom = Offset::Begin(size); + margin.right = Offset::Begin(size); + } + + if direction.right && direction.top { + margin.top = Offset::Begin(size); + margin.right = Offset::End(size); + } + + if direction.left && direction.bottom { + margin.bottom = Offset::End(size); + margin.left = Offset::Begin(size); + } + + if direction.left && direction.top { + margin.top = Offset::End(size); + margin.left = Offset::End(size); + } + + cfg.set_margin_offset(margin); +} + +fn set_margin_color(cfg: &mut ColoredConfig, color: AnsiColor<'static>, direction: &Sides<bool>) { + let mut margin: Sides<Option<AnsiColor<'static>>> = Sides::default(); + if direction.right { + margin.right = Some(color.clone()); + } + + if direction.top { + margin.top = Some(color.clone()); + } + + if direction.left { + margin.left = Some(color.clone()); + } + + if direction.bottom { + margin.bottom = Some(color.clone()); + } + + cfg.set_margin_color(margin); +} diff --git a/vendor/tabled/src/settings/span/column.rs b/vendor/tabled/src/settings/span/column.rs new file mode 100644 index 000000000..50af64c23 --- /dev/null +++ b/vendor/tabled/src/settings/span/column.rs @@ -0,0 +1,125 @@ +use crate::{ + grid::{ + config::{ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Columns (Vertical) span. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct ColumnSpan { + size: usize, +} + +impl ColumnSpan { + /// Creates a new column (vertical) span. + pub fn new(size: usize) -> Self { + Self { size } + } + + /// Creates a new column (vertical) span with a maximux value possible. + pub fn max() -> Self { + Self::new(usize::MAX) + } +} + +impl<R> CellOption<R, ColoredConfig> for ColumnSpan +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + set_col_spans(cfg, self.size, entity, (count_rows, count_cols)); + remove_false_spans(cfg); + } +} + +fn set_col_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) { + for pos in entity.iter(shape.0, shape.1) { + if !is_valid_pos(pos, shape) { + continue; + } + + let mut span = span; + if !is_column_span_valid(pos.1, span, shape.1) { + span = shape.1 - pos.1; + } + + if span_has_intersections(cfg, pos, span) { + continue; + } + + set_span_column(cfg, pos, span); + } +} + +fn set_span_column(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) { + if span == 0 { + let (row, col) = pos; + if col == 0 { + return; + } + + if let Some(closecol) = closest_visible(cfg, (row, col - 1)) { + let span = col + 1 - closecol; + cfg.set_column_span((row, closecol), span); + } + } + + cfg.set_column_span(pos, span); +} + +fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> { + loop { + if cfg.is_cell_visible(pos) { + return Some(pos.1); + } + + if pos.1 == 0 { + return None; + } + + pos.1 -= 1; + } +} + +fn is_column_span_valid(col: usize, span: usize, count_cols: usize) -> bool { + span + col <= count_cols +} + +fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool { + row < count_rows && col < count_cols +} + +fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool { + for col in col..col + span { + if !cfg.is_cell_visible((row, col)) { + return true; + } + } + + false +} + +fn remove_false_spans(cfg: &mut SpannedConfig) { + for (pos, _) in cfg.get_column_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } + + for (pos, _) in cfg.get_row_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } +} diff --git a/vendor/tabled/src/settings/span/mod.rs b/vendor/tabled/src/settings/span/mod.rs new file mode 100644 index 000000000..1139a2b99 --- /dev/null +++ b/vendor/tabled/src/settings/span/mod.rs @@ -0,0 +1,68 @@ +//! This module contains a [`Span`] settings, it helps to +//! make a cell take more space then it generally takes. +//! +//! # Example +//! +//! ``` +//! use tabled::{settings::{Span, Modify}, Table}; +//! +//! let data = [[1, 2, 3], [4, 5, 6]]; +//! +//! let table = Table::new(data) +//! .with(Modify::new((2, 0)).with(Span::column(2))) +//! .with(Modify::new((0, 1)).with(Span::column(2))) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+---+---+---+\n", +//! "| 0 | 1 |\n", +//! "+---+---+---+\n", +//! "| 1 | 2 | 3 |\n", +//! "+---+---+---+\n", +//! "| 4 | 6 |\n", +//! "+---+---+---+", +//! ) +//! ) +//! ``` + +mod column; +mod row; + +pub use column::ColumnSpan; +pub use row::RowSpan; + +/// Span represent a horizontal/column span setting for any cell on a [`Table`]. +/// +/// It will be ignored if: +/// - cell position is out of scope +/// - size is bigger then the total number of columns. +/// - size is bigger then the total number of rows. +/// +/// ```rust,no_run +/// # use tabled::{Table, settings::{Style, Span, Modify, object::Columns}}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Modify::new(Columns::single(0)).with(Span::column(2))); +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Span; + +impl Span { + /// New constructs a horizontal/column [`Span`]. + /// + /// If size is bigger then the total number of columns it will be ignored. + pub fn column(size: usize) -> ColumnSpan { + ColumnSpan::new(size) + } + + /// New constructs a vertical/row [`Span`]. + /// + /// If size is bigger then the total number of rows it will be ignored. + pub fn row(size: usize) -> RowSpan { + RowSpan::new(size) + } +} diff --git a/vendor/tabled/src/settings/span/row.rs b/vendor/tabled/src/settings/span/row.rs new file mode 100644 index 000000000..68673f7a2 --- /dev/null +++ b/vendor/tabled/src/settings/span/row.rs @@ -0,0 +1,126 @@ +use crate::{ + grid::{ + config::{ColoredConfig, Entity, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Row (vertical) span. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct RowSpan { + size: usize, +} + +impl RowSpan { + /// Creates a new row (vertical) span. + pub const fn new(size: usize) -> Self { + Self { size } + } + + /// Creates a new row (vertical) span with a maximux value possible. + pub const fn max() -> Self { + Self::new(usize::MAX) + } +} + +impl<R> CellOption<R, ColoredConfig> for RowSpan +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_cols = records.count_columns(); + + set_row_spans(cfg, self.size, entity, (count_rows, count_cols)); + remove_false_spans(cfg); + } +} + +fn set_row_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) { + for pos in entity.iter(shape.0, shape.1) { + if !is_valid_pos(pos, shape) { + continue; + } + + let mut span = span; + if !is_row_span_valid(pos.0, span, shape.0) { + span = shape.0 - pos.0; + } + + if span_has_intersections(cfg, pos, span) { + continue; + } + + set_span_row(cfg, pos, span); + } +} + +fn set_span_row(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) { + if span == 0 { + let (row, col) = pos; + if row == 0 { + return; + } + + if let Some(closerow) = closest_visible_row(cfg, (row - 1, col)) { + let span = row + 1 - closerow; + cfg.set_row_span((closerow, col), span); + } + } + + cfg.set_row_span(pos, span); +} + +fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> { + loop { + if cfg.is_cell_visible(pos) { + return Some(pos.0); + } + + if pos.0 == 0 { + // can happen if we have a above horizontal spanned cell + return None; + } + + pos.0 -= 1; + } +} + +fn is_row_span_valid(row: usize, span: usize, count_rows: usize) -> bool { + span + row <= count_rows +} + +fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool { + row < count_rows && col < count_cols +} + +fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool { + for row in row..row + span { + if !cfg.is_cell_visible((row, col)) { + return true; + } + } + + false +} + +fn remove_false_spans(cfg: &mut SpannedConfig) { + for (pos, _) in cfg.get_column_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } + + for (pos, _) in cfg.get_row_spans() { + if cfg.is_cell_visible(pos) { + continue; + } + + cfg.set_row_span(pos, 1); + cfg.set_column_span(pos, 1); + } +} diff --git a/vendor/tabled/src/settings/split/mod.rs b/vendor/tabled/src/settings/split/mod.rs new file mode 100644 index 000000000..32f820aef --- /dev/null +++ b/vendor/tabled/src/settings/split/mod.rs @@ -0,0 +1,420 @@ +//! This module contains a [`Split`] setting which is used to +//! format the cells of a [`Table`] by a provided index, direction, behavior, and display preference. +//! +//! [`Table`]: crate::Table + +use core::ops::Range; + +use papergrid::{config::Position, records::PeekableRecords}; + +use crate::grid::records::{ExactRecords, Records, Resizable}; + +use super::TableOption; + +#[derive(Debug, Clone, Copy)] +enum Direction { + Column, + Row, +} + +#[derive(Debug, Clone, Copy)] +enum Behavior { + Concat, + Zip, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum Display { + Clean, + Retain, +} + +/// Returns a new [`Table`] formatted with several optional parameters. +/// +/// The required index parameter determines how many columns/rows a table will be redistributed into. +/// +/// - index +/// - direction +/// - behavior +/// - display +/// +/// # Example +/// +/// ```rust,no_run +/// use std::iter::FromIterator; +/// use tabled::{ +/// settings::split::Split, +/// Table, +/// }; +/// +/// let mut table = Table::from_iter(['a'..='z']); +/// let table = table.with(Split::column(4)).to_string(); +/// +/// assert_eq!(table, "+---+---+---+---+\n\ +/// | a | b | c | d |\n\ +/// +---+---+---+---+\n\ +/// | e | f | g | h |\n\ +/// +---+---+---+---+\n\ +/// | i | j | k | l |\n\ +/// +---+---+---+---+\n\ +/// | m | n | o | p |\n\ +/// +---+---+---+---+\n\ +/// | q | r | s | t |\n\ +/// +---+---+---+---+\n\ +/// | u | v | w | x |\n\ +/// +---+---+---+---+\n\ +/// | y | z | | |\n\ +/// +---+---+---+---+") +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Clone, Copy)] +pub struct Split { + direction: Direction, + behavior: Behavior, + display: Display, + index: usize, +} + +impl Split { + /// Returns a new [`Table`] split on the column at the provided index. + /// + /// The column found at that index becomes the new right-most column in the returned table. + /// Columns found beyond the index are redistributed into the table based on other defined + /// parameters. + /// + /// ```rust,no_run + /// # use tabled::settings::split::Split; + /// Split::column(4); + /// ``` + /// + /// [`Table`]: crate::Table + pub fn column(index: usize) -> Self { + Split { + direction: Direction::Column, + behavior: Behavior::Zip, + display: Display::Clean, + index, + } + } + + /// Returns a new [`Table`] split on the row at the provided index. + /// + /// The row found at that index becomes the new bottom row in the returned table. + /// Rows found beyond the index are redistributed into the table based on other defined + /// parameters. + /// + /// ```rust,no_run + /// # use tabled::settings::split::Split; + /// Split::row(4); + /// ``` + /// + /// [`Table`]: crate::Table + pub fn row(index: usize) -> Self { + Split { + direction: Direction::Row, + behavior: Behavior::Zip, + display: Display::Clean, + index, + } + } + + /// Returns a split [`Table`] with the redistributed cells pushed to the back of the new shape. + /// + /// ```text + /// +---+---+ + /// | a | b | + /// +---+---+ + /// +---+---+---+---+---+ | f | g | + /// | a | b | c | d | e | Split::column(2).concat() +---+---+ + /// +---+---+---+---+---+ => | c | d | + /// | f | g | h | i | j | +---+---+ + /// +---+---+---+---+---+ | h | i | + /// +---+---+ + /// | e | | + /// +---+---+ + /// | j | | + /// +---+---+ + /// ``` + /// + /// [`Table`]: crate::Table + pub fn concat(self) -> Self { + Self { + behavior: Behavior::Concat, + ..self + } + } + + /// Returns a split [`Table`] with the redistributed cells inserted behind + /// the first correlating column/row one after another. + /// + /// ```text + /// +---+---+ + /// | a | b | + /// +---+---+ + /// +---+---+---+---+---+ | c | d | + /// | a | b | c | d | e | Split::column(2).zip() +---+---+ + /// +---+---+---+---+---+ => | e | | + /// | f | g | h | i | j | +---+---+ + /// +---+---+---+---+---+ | f | g | + /// +---+---+ + /// | h | i | + /// +---+---+ + /// | j | | + /// +---+---+ + /// ``` + /// + /// [`Table`]: crate::Table + pub fn zip(self) -> Self { + Self { + behavior: Behavior::Zip, + ..self + } + } + + /// Returns a split [`Table`] with the empty columns/rows filtered out. + /// + /// ```text + /// + /// + /// +---+---+---+ + /// +---+---+---+---+---+ | a | b | c | + /// | a | b | c | d | e | Split::column(3).clean() +---+---+---+ + /// +---+---+---+---+---+ => | d | e | | + /// | f | g | h | | | +---+---+---+ + /// +---+---+---+---+---+ | f | g | h | + /// ^ ^ +---+---+---+ + /// these cells are filtered + /// from the resulting table + /// ``` + /// + /// ## Notes + /// + /// This is apart of the default configuration for Split. + /// + /// See [`retain`] for an alternative display option. + /// + /// [`Table`]: crate::Table + /// [`retain`]: crate::settings::split::Split::retain + pub fn clean(self) -> Self { + Self { + display: Display::Clean, + ..self + } + } + + /// Returns a split [`Table`] with all cells retained. + /// + /// ```text + /// +---+---+---+ + /// | a | b | c | + /// +---+---+---+ + /// +---+---+---+---+---+ | d | e | | + /// | a | b | c | d | e | Split::column(3).retain() +---+---+---+ + /// +---+---+---+---+---+ => | f | g | h | + /// | f | g | h | | | +---+---+---+ + /// +---+---+---+---+---+ |-----------> | | | | + /// ^ ^ | +---+---+---+ + /// |___|_____cells are kept! + /// ``` + /// + /// ## Notes + /// + /// See [`clean`] for an alternative display option. + /// + /// [`Table`]: crate::Table + /// [`clean`]: crate::settings::split::Split::clean + pub fn retain(self) -> Self { + Self { + display: Display::Retain, + ..self + } + } +} + +impl<R, D, Cfg> TableOption<R, D, Cfg> for Split +where + R: Records + ExactRecords + Resizable + PeekableRecords, +{ + fn change(self, records: &mut R, _: &mut Cfg, _: &mut D) { + // variables + let Split { + direction, + behavior, + display, + index: section_length, + } = self; + let mut filtered_sections = 0; + + // early return check + if records.count_columns() == 0 || records.count_rows() == 0 || section_length == 0 { + return; + } + + // computed variables + let (primary_length, secondary_length) = compute_length_arrangement(records, direction); + let sections_per_direction = ceil_div(primary_length, section_length); + let (outer_range, inner_range) = + compute_range_order(secondary_length, sections_per_direction, behavior); + + // work + for outer_index in outer_range { + let from_secondary_index = outer_index * sections_per_direction - filtered_sections; + for inner_index in inner_range.clone() { + let (section_index, from_secondary_index, to_secondary_index) = + compute_range_variables( + outer_index, + inner_index, + secondary_length, + from_secondary_index, + sections_per_direction, + filtered_sections, + behavior, + ); + + match (direction, behavior) { + (Direction::Column, Behavior::Concat) => records.push_row(), + (Direction::Column, Behavior::Zip) => records.insert_row(to_secondary_index), + (Direction::Row, Behavior::Concat) => records.push_column(), + (Direction::Row, Behavior::Zip) => records.insert_column(to_secondary_index), + } + + let section_is_empty = copy_section( + records, + section_length, + section_index, + primary_length, + from_secondary_index, + to_secondary_index, + direction, + ); + + if section_is_empty && display == Display::Clean { + delete(records, to_secondary_index, direction); + filtered_sections += 1; + } + } + } + + cleanup(records, section_length, primary_length, direction); + } +} + +/// Determine which direction should be considered the primary, and which the secondary based on direction +fn compute_length_arrangement<R>(records: &mut R, direction: Direction) -> (usize, usize) +where + R: Records + ExactRecords, +{ + match direction { + Direction::Column => (records.count_columns(), records.count_rows()), + Direction::Row => (records.count_rows(), records.count_columns()), + } +} + +/// reduce the table size to the length of the index in the specified direction +fn cleanup<R>(records: &mut R, section_length: usize, primary_length: usize, direction: Direction) +where + R: Resizable, +{ + for segment in (section_length..primary_length).rev() { + match direction { + Direction::Column => records.remove_column(segment), + Direction::Row => records.remove_row(segment), + } + } +} + +/// Delete target index row or column +fn delete<R>(records: &mut R, target_index: usize, direction: Direction) +where + R: Resizable, +{ + match direction { + Direction::Column => records.remove_row(target_index), + Direction::Row => records.remove_column(target_index), + } +} + +/// copy cells to new location +/// +/// returns if the copied section was entirely blank +fn copy_section<R>( + records: &mut R, + section_length: usize, + section_index: usize, + primary_length: usize, + from_secondary_index: usize, + to_secondary_index: usize, + direction: Direction, +) -> bool +where + R: ExactRecords + Resizable + PeekableRecords, +{ + let mut section_is_empty = true; + for to_primary_index in 0..section_length { + let from_primary_index = to_primary_index + section_index * section_length; + + if from_primary_index < primary_length { + let from_position = + format_position(direction, from_primary_index, from_secondary_index); + if records.get_text(from_position) != "" { + section_is_empty = false; + } + records.swap( + from_position, + format_position(direction, to_primary_index, to_secondary_index), + ); + } + } + section_is_empty +} + +/// determine section over direction or vice versa based on behavior +fn compute_range_order( + direction_length: usize, + sections_per_direction: usize, + behavior: Behavior, +) -> (Range<usize>, Range<usize>) { + match behavior { + Behavior::Concat => (1..sections_per_direction, 0..direction_length), + Behavior::Zip => (0..direction_length, 1..sections_per_direction), + } +} + +/// helper function for shimming both behaviors to work within a single nested loop +fn compute_range_variables( + outer_index: usize, + inner_index: usize, + direction_length: usize, + from_secondary_index: usize, + sections_per_direction: usize, + filtered_sections: usize, + behavior: Behavior, +) -> (usize, usize, usize) { + match behavior { + Behavior::Concat => ( + outer_index, + inner_index, + inner_index + outer_index * direction_length - filtered_sections, + ), + Behavior::Zip => ( + inner_index, + from_secondary_index, + outer_index * sections_per_direction + inner_index - filtered_sections, + ), + } +} + +/// utility for arguments of a position easily +fn format_position(direction: Direction, primary_index: usize, secondary_index: usize) -> Position { + match direction { + Direction::Column => (secondary_index, primary_index), + Direction::Row => (primary_index, secondary_index), + } +} + +/// ceil division utility because the std lib ceil_div isn't stable yet +fn ceil_div(x: usize, y: usize) -> usize { + debug_assert!(x != 0); + 1 + ((x - 1) / y) +} diff --git a/vendor/tabled/src/settings/style/border.rs b/vendor/tabled/src/settings/style/border.rs new file mode 100644 index 000000000..a8cbefb5d --- /dev/null +++ b/vendor/tabled/src/settings/style/border.rs @@ -0,0 +1,159 @@ +use crate::{ + grid::{ + config::{Border as GBorder, ColoredConfig, Entity}, + records::{ExactRecords, Records}, + }, + settings::CellOption, +}; + +/// Border represents a border of a Cell. +/// +/// ```text +/// top border +/// | +/// V +/// corner top left ------> +_______+ <---- corner top left +/// | | +/// left border ----------> | cell | <---- right border +/// | | +/// corner bottom right --> +_______+ <---- corner bottom right +/// ^ +/// | +/// bottom border +/// ``` +/// +/// ```rust,no_run +/// # use tabled::{Table, settings::{Modify, style::{Style, Border}, object::Rows}}; +/// # let data: Vec<&'static str> = Vec::new(); +/// let table = Table::new(&data) +/// .with(Style::ascii()) +/// .with(Modify::new(Rows::single(0)).with(Border::default().top('x'))); +/// ``` +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Border(GBorder<char>); + +impl Border { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub const fn full( + top: char, + bottom: char, + left: char, + right: char, + top_left: char, + top_right: char, + bottom_left: char, + bottom_right: char, + ) -> Self { + Self(GBorder::full( + top, + bottom, + left, + right, + top_left, + top_right, + bottom_left, + bottom_right, + )) + } + + /// This function constructs a cell borders with all sides's char set to a given character. + /// It behaves like [`Border::full`] with the same character set to each side. + pub const fn filled(c: char) -> Self { + Self::full(c, c, c, c, c, c, c, c) + } + + /// Using this function you deconstruct the existing borders. + pub const fn empty() -> EmptyBorder { + EmptyBorder + } + + /// Set a top border character. + pub const fn top(mut self, c: char) -> Self { + self.0.top = Some(c); + self + } + + /// Set a bottom border character. + pub const fn bottom(mut self, c: char) -> Self { + self.0.bottom = Some(c); + self + } + + /// Set a left border character. + pub const fn left(mut self, c: char) -> Self { + self.0.left = Some(c); + self + } + + /// Set a right border character. + pub const fn right(mut self, c: char) -> Self { + self.0.right = Some(c); + self + } + + /// Set a top left intersection character. + pub const fn corner_top_left(mut self, c: char) -> Self { + self.0.left_top_corner = Some(c); + self + } + + /// Set a top right intersection character. + pub const fn corner_top_right(mut self, c: char) -> Self { + self.0.right_top_corner = Some(c); + self + } + + /// Set a bottom left intersection character. + pub const fn corner_bottom_left(mut self, c: char) -> Self { + self.0.left_bottom_corner = Some(c); + self + } + + /// Set a bottom right intersection character. + pub const fn corner_bottom_right(mut self, c: char) -> Self { + self.0.right_bottom_corner = Some(c); + self + } +} + +impl<R> CellOption<R, ColoredConfig> for Border +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let shape = (records.count_rows(), records.count_columns()); + + for pos in entity.iter(shape.0, shape.1) { + cfg.set_border(pos, self.0); + } + } +} + +impl From<GBorder<char>> for Border { + fn from(b: GBorder<char>) -> Border { + Border(b) + } +} + +impl From<Border> for GBorder<char> { + fn from(value: Border) -> Self { + value.0 + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct EmptyBorder; + +impl<R> CellOption<R, ColoredConfig> for EmptyBorder +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let shape = (records.count_rows(), records.count_columns()); + + for pos in entity.iter(shape.0, shape.1) { + cfg.remove_border(pos, shape); + } + } +} diff --git a/vendor/tabled/src/settings/style/border_char.rs b/vendor/tabled/src/settings/style/border_char.rs new file mode 100644 index 000000000..b1e04c579 --- /dev/null +++ b/vendor/tabled/src/settings/style/border_char.rs @@ -0,0 +1,99 @@ +use crate::{ + grid::config::{ColoredConfig, Entity, Position, SpannedConfig}, + grid::records::{ExactRecords, Records}, + settings::CellOption, +}; + +use super::Offset; + +/// [`BorderChar`] sets a char to a specific location on a horizontal line. +/// +/// # Example +/// +/// ```rust +/// use tabled::{Table, settings::{style::{Style, BorderChar, Offset}, Modify, object::{Object, Rows, Columns}}}; +/// +/// let mut table = Table::new(["Hello World"]); +/// table +/// .with(Style::markdown()) +/// .with(Modify::new(Rows::single(1)) +/// .with(BorderChar::horizontal(':', Offset::Begin(0))) +/// .with(BorderChar::horizontal(':', Offset::End(0))) +/// ) +/// .with(Modify::new((1, 0).and((1, 1))).with(BorderChar::vertical('#', Offset::Begin(0)))); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "| &str |\n", +/// "|:-----------:|\n", +/// "# Hello World #", +/// ), +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct BorderChar { + c: char, + offset: Offset, + horizontal: bool, +} + +impl BorderChar { + /// Creates a [`BorderChar`] which overrides horizontal line. + pub fn horizontal(c: char, offset: Offset) -> Self { + Self { + c, + offset, + horizontal: true, + } + } + + /// Creates a [`BorderChar`] which overrides vertical line. + pub fn vertical(c: char, offset: Offset) -> Self { + Self { + c, + offset, + horizontal: false, + } + } +} + +impl<R> CellOption<R, ColoredConfig> for BorderChar +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let cells = entity.iter(records.count_rows(), records.count_columns()); + + match self.horizontal { + true => add_char_horizontal(cfg, self.c, self.offset, cells), + false => add_char_vertical(cfg, self.c, self.offset, cells), + } + } +} + +fn add_char_vertical<I: Iterator<Item = Position>>( + cfg: &mut SpannedConfig, + c: char, + offset: Offset, + cells: I, +) { + let offset = offset.into(); + + for pos in cells { + cfg.set_vertical_char(pos, c, offset); + } +} + +fn add_char_horizontal<I: Iterator<Item = Position>>( + cfg: &mut SpannedConfig, + c: char, + offset: Offset, + cells: I, +) { + let offset = offset.into(); + + for pos in cells { + cfg.set_horizontal_char(pos, c, offset); + } +} diff --git a/vendor/tabled/src/settings/style/border_color.rs b/vendor/tabled/src/settings/style/border_color.rs new file mode 100644 index 000000000..6b3fa2b1a --- /dev/null +++ b/vendor/tabled/src/settings/style/border_color.rs @@ -0,0 +1,155 @@ +//! This module contains a configuration of a Border to set its color via [`BorderColor`]. + +use crate::{ + grid::{ + color::AnsiColor, + config::{Border, ColoredConfig, Entity}, + records::{ExactRecords, Records}, + }, + settings::{color::Color, CellOption, TableOption}, +}; + +/// BorderColored represents a colored border of a Cell. +/// +/// ```rust,no_run +/// # use tabled::{settings::{style::BorderColor, Style, Color, object::Rows, Modify}, Table}; +/// # +/// # let data: Vec<&'static str> = Vec::new(); +/// # +/// let table = Table::new(&data) +/// .with(Style::ascii()) +/// .with(Modify::new(Rows::single(0)).with(BorderColor::default().top(Color::FG_RED))); +/// ``` +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct BorderColor(Border<AnsiColor<'static>>); + +impl BorderColor { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub fn full( + top: Color, + bottom: Color, + left: Color, + right: Color, + top_left: Color, + top_right: Color, + bottom_left: Color, + bottom_right: Color, + ) -> Self { + Self(Border::full( + top.into(), + bottom.into(), + left.into(), + right.into(), + top_left.into(), + top_right.into(), + bottom_left.into(), + bottom_right.into(), + )) + } + + /// This function constructs a cell borders with all sides's char set to a given character. + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn filled(c: Color) -> Self { + let c: AnsiColor<'_> = c.into(); + + Self(Border { + top: Some(c.clone()), + bottom: Some(c.clone()), + left: Some(c.clone()), + right: Some(c.clone()), + left_bottom_corner: Some(c.clone()), + left_top_corner: Some(c.clone()), + right_bottom_corner: Some(c.clone()), + right_top_corner: Some(c), + }) + } + + /// Set a top border character. + pub fn top(mut self, c: Color) -> Self { + self.0.top = Some(c.into()); + self + } + + /// Set a bottom border character. + pub fn bottom(mut self, c: Color) -> Self { + self.0.bottom = Some(c.into()); + self + } + + /// Set a left border character. + pub fn left(mut self, c: Color) -> Self { + self.0.left = Some(c.into()); + self + } + + /// Set a right border character. + pub fn right(mut self, c: Color) -> Self { + self.0.right = Some(c.into()); + self + } + + /// Set a top left intersection character. + pub fn corner_top_left(mut self, c: Color) -> Self { + self.0.left_top_corner = Some(c.into()); + self + } + + /// Set a top right intersection character. + pub fn corner_top_right(mut self, c: Color) -> Self { + self.0.right_top_corner = Some(c.into()); + self + } + + /// Set a bottom left intersection character. + pub fn corner_bottom_left(mut self, c: Color) -> Self { + self.0.left_bottom_corner = Some(c.into()); + self + } + + /// Set a bottom right intersection character. + pub fn corner_bottom_right(mut self, c: Color) -> Self { + self.0.right_bottom_corner = Some(c.into()); + self + } +} + +impl<R> CellOption<R, ColoredConfig> for BorderColor +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let border_color = &self.0; + + for pos in entity.iter(count_rows, count_columns) { + cfg.set_border_color(pos, border_color.clone()); + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderColor +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let border_color = &self.0; + + for row in 0..count_rows { + for col in 0..count_columns { + cfg.set_border_color((row, col), border_color.clone()); + } + } + } +} + +impl From<BorderColor> for Border<AnsiColor<'static>> { + fn from(val: BorderColor) -> Self { + val.0 + } +} diff --git a/vendor/tabled/src/settings/style/border_text.rs b/vendor/tabled/src/settings/style/border_text.rs new file mode 100644 index 000000000..163a19821 --- /dev/null +++ b/vendor/tabled/src/settings/style/border_text.rs @@ -0,0 +1,253 @@ +use crate::{ + grid::{ + color::AnsiColor, + config::{self, ColoredConfig, SpannedConfig}, + dimension::{Dimension, Estimate}, + records::{ExactRecords, Records}, + }, + settings::{ + object::{FirstRow, LastRow}, + Color, TableOption, + }, +}; + +use super::Offset; + +/// [`BorderText`] writes a custom text on a border. +/// +/// # Example +/// +/// ```rust +/// use tabled::{Table, settings::style::BorderText, settings::object::Rows}; +/// +/// let mut table = Table::new(["Hello World"]); +/// table +/// .with(BorderText::new("+-.table").horizontal(Rows::first())); +/// +/// assert_eq!( +/// table.to_string(), +/// "+-.table------+\n\ +/// | &str |\n\ +/// +-------------+\n\ +/// | Hello World |\n\ +/// +-------------+" +/// ); +/// ``` +#[derive(Debug)] +pub struct BorderText<L> { + text: String, + offset: Offset, + color: Option<AnsiColor<'static>>, + line: L, +} + +impl BorderText<()> { + /// Creates a [`BorderText`] instance. + /// + /// Lines are numbered from 0 to the `count_rows` included + /// (`line >= 0 && line <= count_rows`). + pub fn new<S: Into<String>>(text: S) -> Self { + BorderText { + text: text.into(), + line: (), + offset: Offset::Begin(0), + color: None, + } + } +} + +impl<Line> BorderText<Line> { + /// Set a line on which we will set the text. + pub fn horizontal<L>(self, line: L) -> BorderText<L> { + BorderText { + line, + text: self.text, + offset: self.offset, + color: self.color, + } + } + + /// Set an offset from which the text will be started. + pub fn offset(self, offset: Offset) -> Self { + BorderText { + offset, + text: self.text, + line: self.line, + color: self.color, + } + } + + /// Set a color of the text. + pub fn color(self, color: Color) -> Self { + BorderText { + color: Some(color.into()), + text: self.text, + line: self.line, + offset: self.offset, + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<usize> +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = self.line; + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<FirstRow> +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = 0; + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderText<LastRow> +where + R: Records + ExactRecords, + for<'a> &'a R: Records, + for<'a> D: Estimate<&'a R, ColoredConfig>, + D: Dimension, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(records, cfg); + let shape = (records.count_rows(), records.count_columns()); + let line = records.count_rows(); + set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); + } +} + +fn set_horizontal_chars<D: Dimension>( + cfg: &mut SpannedConfig, + dims: &D, + offset: Offset, + line: usize, + text: &str, + color: &Option<AnsiColor<'static>>, + shape: (usize, usize), +) { + let (_, count_columns) = shape; + let pos = get_start_pos(cfg, dims, offset, count_columns); + let pos = match pos { + Some(pos) => pos, + None => return, + }; + + let mut chars = text.chars(); + let mut i = cfg.has_vertical(0, count_columns) as usize; + if i == 1 && pos == 0 { + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + let mut b = cfg.get_border((line, 0), shape); + b.left_top_corner = b.left_top_corner.map(|_| c); + cfg.set_border((line, 0), b); + + if let Some(color) = color.as_ref() { + let mut b = cfg.get_border_color((line, 0), shape).cloned(); + b.left_top_corner = Some(color.clone()); + cfg.set_border_color((line, 0), b); + } + } + + for col in 0..count_columns { + let w = dims.get_width(col); + if i + w > pos { + for off in 0..w { + if i + off < pos { + continue; + } + + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + cfg.set_horizontal_char((line, col), c, config::Offset::Begin(off)); + if let Some(color) = color.as_ref() { + cfg.set_horizontal_color( + (line, col), + color.clone(), + config::Offset::Begin(off), + ); + } + } + } + + i += w; + + if cfg.has_vertical(col + 1, count_columns) { + i += 1; + + if i > pos { + let c = match chars.next() { + Some(c) => c, + None => return, + }; + + let mut b = cfg.get_border((line, col), shape); + b.right_top_corner = b.right_top_corner.map(|_| c); + cfg.set_border((line, col), b); + + if let Some(color) = color.as_ref() { + let mut b = cfg.get_border_color((line, col), shape).cloned(); + b.right_top_corner = Some(color.clone()); + cfg.set_border_color((line, col), b); + } + } + } + } +} + +fn get_start_pos<D: Dimension>( + cfg: &SpannedConfig, + dims: &D, + offset: Offset, + count_columns: usize, +) -> Option<usize> { + let totalw = total_width(cfg, dims, count_columns); + match offset { + Offset::Begin(i) => { + if i > totalw { + None + } else { + Some(i) + } + } + Offset::End(i) => { + if i > totalw { + None + } else { + Some(totalw - i) + } + } + } +} + +fn total_width<D: Dimension>(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize { + let mut totalw = cfg.has_vertical(0, count_columns) as usize; + for col in 0..count_columns { + totalw += dims.get_width(col); + totalw += cfg.has_vertical(col + 1, count_columns) as usize; + } + + totalw +} diff --git a/vendor/tabled/src/settings/style/builder.rs b/vendor/tabled/src/settings/style/builder.rs new file mode 100644 index 000000000..d55c400cc --- /dev/null +++ b/vendor/tabled/src/settings/style/builder.rs @@ -0,0 +1,1208 @@ +//! This module contains a compile time style builder [`Style`]. + +use core::marker::PhantomData; + +use crate::{ + grid::config::{Borders, CompactConfig, CompactMultilineConfig}, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::config::ColoredConfig; + +use super::{HorizontalLine, Line, VerticalLine}; + +/// Style is represents a theme of a [`Table`]. +/// +/// ```text +/// corner top left top intersection corner top right +/// . | . +/// . V . +/// ╭───┬───┬───┬───┬───┬───┬────┬────┬────╮ +/// │ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ +/// ├───┼───┼───┼───┼───┼───┼────┼────┼────┤ <- this horizontal line is custom 'horizontals' +/// │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ other lines horizontal lines are not set they called 'horizontal' +/// │ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ +/// │ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ +/// ╰───┴───┴───┴───┴───┴───┴────┴────┴────╯ +/// . ^ ^ . +/// . | | . +/// corner bottom left | bottom intersection corner bottom right +/// | +/// | +/// all this vertical lines are called 'vertical' +/// ``` +/// +/// +/// ```text +/// ┌───┬───┬───┬───┬───┐ +/// │ 0 │ 1 │ 2 │ 3 │ 4 │ +/// intersection left ->├───X───X───X───X───┤ <- all this horizontal lines are called 'horizontal' +/// │ 1 │ 2 │ 3 │ 4 │ 5 │ +/// ├───X───X───X───X───┤ <- intersection right +/// │ 2 │ 3 │ 4 │ 5 │ 6 │ +/// └───┴───┴───┴───┴───┘ +/// +/// All 'X' positions are called 'intersection'. +/// It's a place where 'vertical' and 'horizontal' lines intersect. +/// ``` +/// +/// It tries to limit an controlling a valid state of it. +/// For example, it won't allow to call method [`Style::corner_top_left`] unless [`Style::left`] and [`Style::top`] is set. +/// +/// You can turn [`Style`] into [`RawStyle`] to have more control using [`Into`] implementation. +/// +/// # Example +/// +#[cfg_attr(feature = "std", doc = "```")] +#[cfg_attr(not(feature = "std"), doc = "```ignore")] +/// use tabled::{Table, settings::Style}; +/// +/// let style = Style::ascii() +/// .bottom('*') +/// .intersection(' '); +/// +/// let data = vec!["Hello", "2021"]; +/// let table = Table::new(&data).with(style).to_string(); +/// +/// println!("{}", table); +/// ``` +/// +/// [`Table`]: crate::Table +/// [`RawStyle`]: crate::settings::style::RawStyle +/// [`Style::corner_top_left`]: Style::corner_top_left +/// [`Style::left`]: Style.left +/// [`Style::top`]: Style.function.top +#[derive(Debug, Clone)] +pub struct Style<T, B, L, R, H, V, HLines = HLineArray<0>, VLines = VLineArray<0>> { + borders: Borders<char>, + horizontals: HLines, + verticals: VLines, + _top: PhantomData<T>, + _bottom: PhantomData<B>, + _left: PhantomData<L>, + _right: PhantomData<R>, + _horizontal: PhantomData<H>, + _vertical: PhantomData<V>, +} + +type HLineArray<const N: usize> = [HorizontalLine; N]; + +type VLineArray<const N: usize> = [VerticalLine; N]; + +/// A marker struct which is used in [`Style`]. +#[derive(Debug, Clone)] +pub struct On; + +impl Style<(), (), (), (), (), (), (), ()> { + /// This style is a style with no styling options on, + /// + /// ```text + /// id destribution link + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ``` + /// + /// Note: The cells in the example have 1-left and 1-right indent. + /// + /// This style can be used as a base style to build a custom one. + /// + /// ```rust,no_run + /// # use tabled::settings::Style; + /// let style = Style::empty() + /// .top('*') + /// .bottom('*') + /// .vertical('#') + /// .intersection_top('*'); + /// ``` + pub const fn empty() -> Style<(), (), (), (), (), ()> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + None, + ), + [], + [], + ) + } + + /// This style is analog of `empty` but with a vertical space(' ') line. + /// + /// ```text + /// id destribution link + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ``` + pub const fn blank() -> Style<(), (), (), (), (), On> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + Some(' '), + ), + [], + [], + ) + } + + /// This is a style which relays only on ASCII charset. + /// + /// It has horizontal and vertical lines. + /// + /// ```text + /// +----+--------------+---------------------------+ + /// | id | destribution | link | + /// +----+--------------+---------------------------+ + /// | 0 | Fedora | https://getfedora.org/ | + /// +----+--------------+---------------------------+ + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// +----+--------------+---------------------------+ + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// +----+--------------+---------------------------+ + /// ``` + pub const fn ascii() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('-', '+', '+', '+'), + Line::full('-', '+', '+', '+'), + Line::full('-', '+', '+', '+'), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } + + /// `psql` style looks like a table style `PostgreSQL` uses. + /// + /// It has only 1 horizontal line which splits header. + /// And no left and right vertical lines. + /// + /// ```text + /// id | destribution | link + /// ----+--------------+--------------------------- + /// 0 | Fedora | https://getfedora.org/ + /// 2 | OpenSUSE | https://www.opensuse.org/ + /// 3 | Endeavouros | https://endeavouros.com/ + /// ``` + pub const fn psql() -> Style<(), (), (), (), (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + None, + None, + Some('|'), + ), + [HorizontalLine::new(1, Line::empty()) + .main(Some('-')) + .intersection(Some('+'))], + [], + ) + } + + /// `markdown` style mimics a `Markdown` table style. + /// + /// ```text + /// | id | destribution | link | + /// |----|--------------|---------------------------| + /// | 0 | Fedora | https://getfedora.org/ | + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// ``` + pub const fn markdown() -> Style<(), (), On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::empty(), + Line::empty(), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [HorizontalLine::new(1, Line::full('-', '|', '|', '|'))], + [], + ) + } + + /// This style is analog of [`Style::ascii`] which uses UTF-8 charset. + /// + /// It has vertical and horizontal split lines. + /// + /// ```text + /// ┌────┬──────────────┬───────────────────────────┐ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// └────┴──────────────┴───────────────────────────┘ + /// ``` + pub const fn modern() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('─', '┬', '┌', '┐'), + Line::full('─', '┴', '└', '┘'), + Line::full('─', '┼', '├', '┤'), + Some('│'), + Some('│'), + Some('│'), + ), + [], + [], + ) + } + + /// This style looks like a [`Style::modern`] but without horozizontal lines except a header. + /// + /// Beware: It uses UTF-8 characters. + /// + /// ```text + /// ┌────┬──────────────┬───────────────────────────┐ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// └────┴──────────────┴───────────────────────────┘ + /// ``` + pub const fn sharp() -> Style<On, On, On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::full('─', '┬', '┌', '┐'), + Line::full('─', '┴', '└', '┘'), + Line::empty(), + Some('│'), + Some('│'), + Some('│'), + ), + [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], + [], + ) + } + + /// This style looks like a [`Style::sharp`] but with rounded corners. + /// + /// Beware: It uses UTF-8 characters. + /// + /// ```text + /// ╭────┬──────────────┬───────────────────────────╮ + /// │ id │ destribution │ link │ + /// ├────┼──────────────┼───────────────────────────┤ + /// │ 0 │ Fedora │ https://getfedora.org/ │ + /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ + /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ + /// ╰────┴──────────────┴───────────────────────────╯ + /// ``` + pub const fn rounded() -> Style<On, On, On, On, (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::full('─', '┬', '╭', '╮'), + Line::full('─', '┴', '╰', '╯'), + Line::empty(), + Some('│'), + Some('│'), + Some('│'), + ), + [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], + [], + ) + } + + /// This style uses a chars which resembles '2 lines'. + /// + /// Beware: It uses UTF8 characters. + /// + /// ```text + /// ╔════╦══════════════╦═══════════════════════════╗ + /// ║ id ║ destribution ║ link ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 0 ║ Fedora ║ https://getfedora.org/ ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 2 ║ OpenSUSE ║ https://www.opensuse.org/ ║ + /// ╠════╬══════════════╬═══════════════════════════╣ + /// ║ 3 ║ Endeavouros ║ https://endeavouros.com/ ║ + /// ╚════╩══════════════╩═══════════════════════════╝ + /// ``` + pub const fn extended() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('═', '╦', '╔', '╗'), + Line::full('═', '╩', '╚', '╝'), + Line::full('═', '╬', '╠', '╣'), + Some('║'), + Some('║'), + Some('║'), + ), + [], + [], + ) + } + + /// This is a style uses only '.' and ':' chars. + /// It has a vertical and horizontal split lines. + /// + /// ```text + /// ................................................. + /// : id : destribution : link : + /// :....:..............:...........................: + /// : 0 : Fedora : https://getfedora.org/ : + /// :....:..............:...........................: + /// : 2 : OpenSUSE : https://www.opensuse.org/ : + /// :....:..............:...........................: + /// : 3 : Endeavouros : https://endeavouros.com/ : + /// :....:..............:...........................: + /// ``` + pub const fn dots() -> Style<On, On, On, On, On, On> { + Style::new( + create_borders( + Line::full('.', '.', '.', '.'), + Line::full('.', ':', ':', ':'), + Line::full('.', ':', ':', ':'), + Some(':'), + Some(':'), + Some(':'), + ), + [], + [], + ) + } + + /// This style is one of table views in `ReStructuredText`. + /// + /// ```text + /// ==== ============== =========================== + /// id destribution link + /// ==== ============== =========================== + /// 0 Fedora https://getfedora.org/ + /// 2 OpenSUSE https://www.opensuse.org/ + /// 3 Endeavouros https://endeavouros.com/ + /// ==== ============== =========================== + /// ``` + pub const fn re_structured_text() -> Style<On, On, (), (), (), On, HLineArray<1>> { + Style::new( + create_borders( + Line::new(Some('='), Some(' '), None, None), + Line::new(Some('='), Some(' '), None, None), + Line::empty(), + None, + None, + Some(' '), + ), + [HorizontalLine::new( + 1, + Line::new(Some('='), Some(' '), None, None), + )], + [], + ) + } + + /// This is a theme analog of [`Style::rounded`], but in using ascii charset and + /// with no horizontal lines. + /// + /// ```text + /// .-----------------------------------------------. + /// | id | destribution | link | + /// | 0 | Fedora | https://getfedora.org/ | + /// | 2 | OpenSUSE | https://www.opensuse.org/ | + /// | 3 | Endeavouros | https://endeavouros.com/ | + /// '-----------------------------------------------' + /// ``` + pub const fn ascii_rounded() -> Style<On, On, On, On, (), On> { + Style::new( + create_borders( + Line::full('-', '-', '.', '.'), + Line::full('-', '-', '\'', '\''), + Line::empty(), + Some('|'), + Some('|'), + Some('|'), + ), + [], + [], + ) + } +} + +impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> { + /// Frame function returns a frame as a border. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::{Style, Highlight, object::Rows}}; + /// + /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]]; + /// let table = Table::new(data) + /// .with(Highlight::new(Rows::first(), Style::modern().get_frame())) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "┌──────────────────┐\n", + /// "│ 0 | 1 │\n", + /// "└──────────────────┘\n", + /// "| 10:52:19 | Hello |\n", + /// "+----------+-------+\n", + /// "| 10:52:20 | World |\n", + /// "+----------+-------+", + /// ) + /// ); + /// ``` + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub const fn get_frame(&self) -> super::Border { + let mut border = super::Border::filled(' '); + + if let Some(c) = self.borders.top { + border = border.top(c); + } + + if let Some(c) = self.borders.bottom { + border = border.bottom(c); + } + + if let Some(c) = self.borders.left { + border = border.left(c); + } + + if let Some(c) = self.borders.right { + border = border.right(c); + } + + if let Some(c) = self.borders.top_left { + border = border.corner_top_left(c); + } + + if let Some(c) = self.borders.bottom_left { + border = border.corner_bottom_left(c); + } + + if let Some(c) = self.borders.top_right { + border = border.corner_top_right(c); + } + + if let Some(c) = self.borders.bottom_right { + border = border.corner_bottom_right(c); + } + + border + } + + /// Get a [`Style`]'s default horizontal line. + /// + /// It doesn't return an overloaded line via [`Style::horizontals`]. + /// + /// # Example + /// + #[cfg_attr(feature = "std", doc = "```")] + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) + /// .with(Style::ascii().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+-------+-------+-----+\n", + /// "| &str | &str | i32 |\n", + /// "├───────┼───────┼─────┤\n", + /// "| Hello | World | 0 |\n", + /// "| Hello | World | 1 |\n", + /// "| Hello | World | 2 |\n", + /// "+-------+-------+-----+", + /// ) + /// ) + /// ``` + pub const fn get_horizontal(&self) -> Line { + Line::new( + self.borders.horizontal, + self.borders.intersection, + self.borders.left_intersection, + self.borders.right_intersection, + ) + } + + /// Get a [`Style`]'s default horizontal line. + /// + /// It doesn't return an overloaded line via [`Style::verticals`]. + /// + /// # Example + /// + #[cfg_attr(feature = "std", doc = "```")] + #[cfg_attr(not(feature = "std"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, VerticalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) + /// .with(Style::ascii().remove_horizontal().verticals([VerticalLine::new(1, Style::modern().get_vertical())])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "+-------┬-------+-----+\n", + /// "| &str │ &str | i32 |\n", + /// "| Hello │ World | 0 |\n", + /// "| Hello │ World | 1 |\n", + /// "| Hello │ World | 2 |\n", + /// "+-------┴-------+-----+", + /// ) + /// ) + /// ``` + pub const fn get_vertical(&self) -> Line { + Line::new( + self.borders.vertical, + self.borders.intersection, + self.borders.top_intersection, + self.borders.bottom_intersection, + ) + } + + /// Sets a top border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn top(mut self, c: char) -> Style<On, B, L, R, H, V, HLines, VLines> + where + for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>, + { + self.borders.top = Some(c); + + if self.borders.has_left() { + self.borders.top_left = Some(c); + } + + if self.borders.has_right() { + self.borders.top_right = Some(c); + } + + if self.borders.has_vertical() { + self.borders.top_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.connector1 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a bottom border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn bottom(mut self, c: char) -> Style<T, On, L, R, H, V, HLines, VLines> + where + for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>, + { + self.borders.bottom = Some(c); + + if self.borders.has_left() { + self.borders.bottom_left = Some(c); + } + + if self.borders.has_right() { + self.borders.bottom_right = Some(c); + } + + if self.borders.has_vertical() { + self.borders.bottom_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.connector2 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a left border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn left(mut self, c: char) -> Style<T, B, On, R, H, V, HLines, VLines> + where + for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>, + { + self.borders.left = Some(c); + + if self.borders.has_top() { + self.borders.top_left = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_left = Some(c); + } + + if self.borders.has_horizontal() { + self.borders.left_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.connector1 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a right border. + /// + /// Any corners and intersections which were set will be overridden. + pub fn right(mut self, c: char) -> Style<T, B, L, On, H, V, HLines, VLines> + where + for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>, + { + self.borders.right = Some(c); + + if self.borders.has_top() { + self.borders.top_right = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_right = Some(c); + } + + if self.borders.has_horizontal() { + self.borders.right_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.connector2 = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a horizontal split line. + /// + /// Any corners and intersections which were set will be overridden. + pub fn horizontal(mut self, c: char) -> Style<T, B, L, R, On, V, HLines, VLines> + where + for<'a> &'a mut VLines: IntoIterator<Item = &'a mut VerticalLine>, + { + self.borders.horizontal = Some(c); + + if self.borders.has_vertical() { + self.borders.intersection = Some(c); + } + + if self.borders.has_left() { + self.borders.left_intersection = Some(c); + } + + if self.borders.has_right() { + self.borders.right_intersection = Some(c); + } + + for vl in &mut self.verticals { + vl.line.intersection = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Sets a vertical split line. + /// + /// Any corners and intersections which were set will be overridden. + pub fn vertical(mut self, c: char) -> Style<T, B, L, R, H, On, HLines, VLines> + where + for<'a> &'a mut HLines: IntoIterator<Item = &'a mut HorizontalLine>, + { + self.borders.vertical = Some(c); + + if self.borders.has_horizontal() { + self.borders.intersection = Some(c); + } + + if self.borders.has_top() { + self.borders.top_intersection = Some(c); + } + + if self.borders.has_bottom() { + self.borders.bottom_intersection = Some(c); + } + + for hl in &mut self.horizontals { + hl.line.intersection = Some(c); + } + + Style::new(self.borders, self.horizontals, self.verticals) + } + + /// Set border horizontal lines. + /// + /// # Example + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(Style::rounded().horizontals((1..4).map(|i| HorizontalLine::new(i, Line::filled('#'))))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "╭───────┬─────╮\n", + /// "│ &str │ i32 │\n", + /// "###############\n", + /// "│ Hello │ 0 │\n", + /// "###############\n", + /// "│ Hello │ 1 │\n", + /// "###############\n", + /// "│ Hello │ 2 │\n", + /// "╰───────┴─────╯", + /// ) + /// ) + /// ``` + pub fn horizontals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, NewLines, VLines> + where + NewLines: IntoIterator<Item = HorizontalLine> + Clone, + { + Style::new(self.borders, lines, self.verticals) + } + + /// Set border vertical lines. + /// + /// # Example + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{Table, settings::style::{Style, VerticalLine, Line}}; + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(Style::rounded().verticals((0..3).map(|i| VerticalLine::new(i, Line::filled('#'))))) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "#───────#─────#\n", + /// "# &str # i32 #\n", + /// "├───────┼─────┤\n", + /// "# Hello # 0 #\n", + /// "# Hello # 1 #\n", + /// "# Hello # 2 #\n", + /// "#───────#─────#", + /// ) + /// ) + /// ``` + pub fn verticals<NewLines>(self, lines: NewLines) -> Style<T, B, L, R, H, V, HLines, NewLines> + where + NewLines: IntoIterator<Item = VerticalLine> + Clone, + { + Style::new(self.borders, self.horizontals, lines) + } + + /// Removes all horizontal lines set by [`Style::horizontals`] + pub fn remove_horizontals(self) -> Style<T, B, L, R, H, V, HLineArray<0>, VLines> { + Style::new(self.borders, [], self.verticals) + } + + /// Removes all verticals lines set by [`Style::verticals`] + pub fn remove_verticals(self) -> Style<T, B, L, R, H, V, HLines, VLineArray<0>> { + Style::new(self.borders, self.horizontals, []) + } +} + +impl<B, R, H, V, HLines, VLines> Style<On, B, On, R, H, V, HLines, VLines> { + /// Sets a top left corner. + pub fn corner_top_left(mut self, c: char) -> Self { + self.borders.top_left = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<B, L, H, V, HLines, VLines> Style<On, B, L, On, H, V, HLines, VLines> { + /// Sets a top right corner. + pub fn corner_top_right(mut self, c: char) -> Self { + self.borders.top_right = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, L, H, V, HLines, VLines> Style<T, On, L, On, H, V, HLines, VLines> { + /// Sets a bottom right corner. + pub fn corner_bottom_right(mut self, c: char) -> Self { + self.borders.bottom_right = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, R, H, V, HLines, VLines> Style<T, On, On, R, H, V, HLines, VLines> { + /// Sets a bottom left corner. + pub fn corner_bottom_left(mut self, c: char) -> Self { + self.borders.bottom_left = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, B, R, V, HLines, VLines> Style<T, B, On, R, On, V, HLines, VLines> { + /// Sets a left intersection char. + pub fn intersection_left(mut self, c: char) -> Self { + self.borders.left_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, B, L, V, HLines, VLines> Style<T, B, L, On, On, V, HLines, VLines> { + /// Sets a right intersection char. + pub fn intersection_right(mut self, c: char) -> Self { + self.borders.right_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<B, L, R, H, HLines, VLines> Style<On, B, L, R, H, On, HLines, VLines> { + /// Sets a top intersection char. + pub fn intersection_top(mut self, c: char) -> Self { + self.borders.top_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, L, R, H, HLines, VLines> Style<T, On, L, R, H, On, HLines, VLines> { + /// Sets a bottom intersection char. + pub fn intersection_bottom(mut self, c: char) -> Self { + self.borders.bottom_intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<T, B, L, R, HLines, VLines> Style<T, B, L, R, On, On, HLines, VLines> { + /// Sets an inner intersection char. + /// A char between horizontal and vertical split lines. + pub fn intersection(mut self, c: char) -> Self { + self.borders.intersection = Some(c); + + Style::new(self.borders, self.horizontals, self.verticals) + } +} + +impl<B, L, R, H, V, HLines, VLines> Style<On, B, L, R, H, V, HLines, VLines> { + /// Removes top border. + pub fn remove_top( + mut self, + ) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>> + where + VLines: IntoIterator<Item = VerticalLine> + Clone, + { + self.borders.top = None; + self.borders.top_intersection = None; + self.borders.top_left = None; + self.borders.top_right = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), false, true, false); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl<T, L, R, H, V, HLines, VLines> Style<T, On, L, R, H, V, HLines, VLines> { + /// Removes bottom border. + pub fn remove_bottom( + mut self, + ) -> Style<T, (), L, R, H, V, HLines, VerticalLineIter<VLines::IntoIter>> + where + VLines: IntoIterator<Item = VerticalLine> + Clone, + { + self.borders.bottom = None; + self.borders.bottom_intersection = None; + self.borders.bottom_left = None; + self.borders.bottom_right = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), false, false, true); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl<T, B, R, H, V, HLines, VLines> Style<T, B, On, R, H, V, HLines, VLines> { + /// Removes left border. + pub fn remove_left( + mut self, + ) -> Style<T, B, (), R, H, V, HorizontalLineIter<HLines::IntoIter>, VLines> + where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + { + self.borders.left = None; + self.borders.left_intersection = None; + self.borders.top_left = None; + self.borders.bottom_left = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, true, false); + Style::new(self.borders, iter, self.verticals) + } +} + +impl<T, B, L, H, V, HLines, VLines> Style<T, B, L, On, H, V, HLines, VLines> { + /// Removes right border. + pub fn remove_right( + mut self, + ) -> Style<T, B, L, (), H, V, HorizontalLineIter<HLines::IntoIter>, VLines> + where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + { + self.borders.right = None; + self.borders.right_intersection = None; + self.borders.top_right = None; + self.borders.bottom_right = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, false, true); + Style::new(self.borders, iter, self.verticals) + } +} + +impl<T, B, L, R, V, HLines, VLines> Style<T, B, L, R, On, V, HLines, VLines> { + /// Removes horizontal split lines. + /// + /// Not including custom split lines. + pub fn remove_horizontal( + mut self, + ) -> Style<T, B, L, R, (), V, HLines, VerticalLineIter<VLines::IntoIter>> + where + VLines: IntoIterator<Item = VerticalLine> + Clone, + { + self.borders.horizontal = None; + self.borders.left_intersection = None; + self.borders.right_intersection = None; + self.borders.intersection = None; + + let iter = VerticalLineIter::new(self.verticals.into_iter(), true, false, false); + Style::new(self.borders, self.horizontals, iter) + } +} + +impl<T, B, L, R, H, HLines, VLines> Style<T, B, L, R, H, On, HLines, VLines> { + /// Removes vertical split lines. + pub fn remove_vertical( + mut self, + ) -> Style<T, B, L, R, H, (), HorizontalLineIter<HLines::IntoIter>, VLines> + where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + { + self.borders.vertical = None; + self.borders.top_intersection = None; + self.borders.bottom_intersection = None; + self.borders.intersection = None; + + let iter = HorizontalLineIter::new(self.horizontals.into_iter(), true, false, false); + Style::new(self.borders, iter, self.verticals) + } +} + +impl<T, B, L, R, H, V, HLines, VLines> Style<T, B, L, R, H, V, HLines, VLines> { + const fn new(borders: Borders<char>, horizontals: HLines, verticals: VLines) -> Self { + Self { + borders, + horizontals, + verticals, + _top: PhantomData, + _bottom: PhantomData, + _left: PhantomData, + _right: PhantomData, + _horizontal: PhantomData, + _vertical: PhantomData, + } + } + + /// Return borders of a table. + pub const fn get_borders(&self) -> &Borders<char> { + &self.borders + } + + /// Return custom horizontals which were set. + pub const fn get_horizontals(&self) -> &HLines { + &self.horizontals + } + + /// Return custom verticals which were set. + pub const fn get_verticals(&self) -> &VLines { + &self.verticals + } +} + +#[cfg(feature = "std")] +impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, ColoredConfig> + for Style<T, B, L, R, H, V, HLines, VLines> +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + VLines: IntoIterator<Item = VerticalLine> + Clone, +{ + fn change(self, records: &mut I, cfg: &mut ColoredConfig, dimension: &mut D) { + cfg.clear_theme(); + + cfg.set_borders(self.borders); + + for hl in self.horizontals { + hl.change(records, cfg, dimension); + } + + for vl in self.verticals { + vl.change(records, cfg, dimension); + } + } +} + +impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, CompactConfig> + for Style<T, B, L, R, H, V, HLines, VLines> +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, +{ + fn change(self, records: &mut I, cfg: &mut CompactConfig, dimension: &mut D) { + *cfg = cfg.set_borders(self.borders); + + let first_line = self.horizontals.into_iter().next(); + if let Some(line) = first_line { + line.change(records, cfg, dimension); + } + } +} + +impl<T, B, L, R, H, V, HLines, VLines, I, D> TableOption<I, D, CompactMultilineConfig> + for Style<T, B, L, R, H, V, HLines, VLines> +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, +{ + fn change(self, records: &mut I, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} + +/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. +#[derive(Debug, Clone)] +pub struct HorizontalLineIter<I> { + iter: I, + intersection: bool, + left: bool, + right: bool, +} + +impl<I> HorizontalLineIter<I> { + fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self { + Self { + iter, + intersection, + left, + right, + } + } +} + +impl<I> Iterator for HorizontalLineIter<I> +where + I: Iterator<Item = HorizontalLine>, +{ + type Item = HorizontalLine; + + fn next(&mut self) -> Option<Self::Item> { + let mut hl = self.iter.next()?; + + if self.intersection { + hl.line.intersection = None; + } + + if self.left { + hl.line.connector1 = None; + } + + if self.right { + hl.line.connector2 = None; + } + + Some(hl) + } +} + +/// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. +#[derive(Debug, Clone)] +pub struct VerticalLineIter<I> { + iter: I, + intersection: bool, + top: bool, + bottom: bool, +} + +impl<I> VerticalLineIter<I> { + fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self { + Self { + iter, + intersection, + top, + bottom, + } + } +} + +impl<I> Iterator for VerticalLineIter<I> +where + I: Iterator<Item = VerticalLine>, +{ + type Item = VerticalLine; + + fn next(&mut self) -> Option<Self::Item> { + let mut hl = self.iter.next()?; + + if self.intersection { + hl.line.intersection = None; + } + + if self.top { + hl.line.connector1 = None; + } + + if self.bottom { + hl.line.connector2 = None; + } + + Some(hl) + } +} + +const fn create_borders( + top: Line, + bottom: Line, + horizontal: Line, + left: Option<char>, + right: Option<char>, + vertical: Option<char>, +) -> Borders<char> { + Borders { + top: top.main, + bottom: bottom.main, + top_left: top.connector1, + top_right: top.connector2, + bottom_left: bottom.connector1, + bottom_right: bottom.connector2, + top_intersection: top.intersection, + bottom_intersection: bottom.intersection, + left_intersection: horizontal.connector1, + right_intersection: horizontal.connector2, + horizontal: horizontal.main, + intersection: horizontal.intersection, + left, + right, + vertical, + } +} diff --git a/vendor/tabled/src/settings/style/horizontal_line.rs b/vendor/tabled/src/settings/style/horizontal_line.rs new file mode 100644 index 000000000..bbcdc3fb4 --- /dev/null +++ b/vendor/tabled/src/settings/style/horizontal_line.rs @@ -0,0 +1,69 @@ +use crate::{ + grid::config::{CompactConfig, CompactMultilineConfig}, + settings::TableOption, +}; + +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, HorizontalLine as GridLine}; + +use super::Line; + +/// A horizontal split line which can be used to set a border. +#[cfg_attr(not(feature = "std"), allow(dead_code))] +#[derive(Debug, Clone)] +pub struct HorizontalLine { + pub(super) index: usize, + pub(super) line: Line, +} + +impl HorizontalLine { + /// Creates a new horizontal split line. + pub const fn new(index: usize, line: Line) -> Self { + Self { index, line } + } + + /// Sets a horizontal character. + pub const fn main(mut self, c: Option<char>) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option<char>) -> Self { + self.line.intersection = c; + self + } + + /// Sets a left character. + pub const fn left(mut self, c: Option<char>) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a right character. + pub const fn right(mut self, c: Option<char>) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl<R, D> TableOption<R, D, ColoredConfig> for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_horizontal_line(self.index, GridLine::from(self.line)) + } +} + +impl<R, D> TableOption<R, D, CompactConfig> for HorizontalLine { + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + if self.index == 1 { + *cfg = cfg.set_first_horizontal_line(papergrid::config::Line::from(self.line)); + } + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for HorizontalLine { + fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { + self.change(records, cfg.as_mut(), dimension) + } +} diff --git a/vendor/tabled/src/settings/style/line.rs b/vendor/tabled/src/settings/style/line.rs new file mode 100644 index 000000000..10b3895c4 --- /dev/null +++ b/vendor/tabled/src/settings/style/line.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "std")] +use crate::grid::config::{HorizontalLine, VerticalLine}; + +/// The structure represent a vertical or horizontal line. +#[derive(Debug, Default, Clone, Copy)] +pub struct Line { + pub(crate) main: Option<char>, + pub(crate) intersection: Option<char>, + pub(crate) connector1: Option<char>, + pub(crate) connector2: Option<char>, +} + +impl Line { + /// Creates a new [`Line`] object. + pub const fn new( + main: Option<char>, + intersection: Option<char>, + connector1: Option<char>, + connector2: Option<char>, + ) -> Self { + Self { + main, + intersection, + connector1, + connector2, + } + } + + /// Creates a new [`Line`] object with all chars set. + pub const fn full(main: char, intersection: char, connector1: char, connector2: char) -> Self { + Self::new( + Some(main), + Some(intersection), + Some(connector1), + Some(connector2), + ) + } + + /// Creates a new [`Line`] object with all chars set to the provided one. + pub const fn filled(c: char) -> Self { + Self::full(c, c, c, c) + } + + /// Creates a new [`Line`] object with all chars not set. + pub const fn empty() -> Self { + Self::new(None, None, None, None) + } + + /// Checks if the line has nothing set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.connector1.is_none() + && self.connector2.is_none() + } +} + +#[cfg(feature = "std")] +impl From<Line> for HorizontalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + left: l.connector1, + right: l.connector2, + } + } +} + +#[cfg(feature = "std")] +impl From<Line> for VerticalLine { + fn from(l: Line) -> Self { + Self { + main: l.main, + intersection: l.intersection, + top: l.connector1, + bottom: l.connector2, + } + } +} + +impl From<Line> for papergrid::config::Line<char> { + fn from(l: Line) -> Self { + Self { + main: l.main.unwrap_or(' '), + intersection: l.intersection, + connect1: l.connector1, + connect2: l.connector2, + } + } +} diff --git a/vendor/tabled/src/settings/style/mod.rs b/vendor/tabled/src/settings/style/mod.rs new file mode 100644 index 000000000..b8d7f688d --- /dev/null +++ b/vendor/tabled/src/settings/style/mod.rs @@ -0,0 +1,127 @@ +//! This module contains a list of primitives which can be applied to change [`Table`] style. +//! +//! ## [`Style`] +//! +//! It is responsible for a table border style. +//! An individual cell border can be set by [`Border`]. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::Style}; +//! +//! let data = vec!["Hello", "2022"]; +//! let mut table = Table::new(&data); +//! table.with(Style::psql()); +//! +//! assert_eq!( +//! table.to_string(), +//! concat!( +//! " &str \n", +//! "-------\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`BorderText`] +//! +//! It's used to override a border with a custom text. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::style::{BorderText, Style}}; +//! +//! let data = vec!["Hello", "2022"]; +//! let table = Table::new(&data) +//! .with(Style::psql()) +//! .with(BorderText::new("Santa").horizontal(1)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! " &str \n", +//! "Santa--\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`Border`] +//! +//! [`Border`] can be used to modify cell's borders. +//! +//! It's possible to set a collored border when `color` feature is on. +//! +//! ### Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use tabled::{Table, settings::{Modify, Style}}; +//! +//! let data = vec!["Hello", "2022"]; +//! let table = Table::new(&data) +//! .with(Style::psql()) +//! .with(Modify::new((0, 0)).with(Style::modern().get_frame())) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "┌───────┐\n", +//! "│ &str │\n", +//! "└───────┘\n", +//! " Hello \n", +//! " 2022 ", +//! ) +//! ) +//! ``` +//! +//! ## [`RawStyle`] +//! +//! A different representation of [`Style`]. +//! With no checks in place. +//! +//! It also contains a list of types to support colors. +//! +//! [`Table`]: crate::Table +//! [`BorderText`]: crate::settings::style::BorderText +//! [`RawStyle`]: crate::settings::style::RawStyle + +#[cfg(feature = "std")] +mod border; +#[cfg(feature = "std")] +mod border_char; +#[cfg(feature = "std")] +mod border_color; +#[cfg(feature = "std")] +mod border_text; +#[cfg(feature = "std")] +mod offset; +#[cfg(feature = "std")] +mod raw_style; +#[cfg(feature = "std")] +mod span_border_correction; + +mod builder; +mod horizontal_line; +mod line; +mod vertical_line; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use self::{ + border::Border, border_char::BorderChar, border_color::BorderColor, border_text::BorderText, + offset::Offset, raw_style::RawStyle, span_border_correction::BorderSpanCorrection, +}; + +pub use builder::{HorizontalLineIter, On, Style, VerticalLineIter}; +pub use horizontal_line::HorizontalLine; +pub use line::Line; +pub use vertical_line::VerticalLine; diff --git a/vendor/tabled/src/settings/style/offset.rs b/vendor/tabled/src/settings/style/offset.rs new file mode 100644 index 000000000..3f8a4f85e --- /dev/null +++ b/vendor/tabled/src/settings/style/offset.rs @@ -0,0 +1,19 @@ +use crate::grid::config; + +/// The structure represents an offset in a text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Offset { + /// An offset from the start. + Begin(usize), + /// An offset from the end. + End(usize), +} + +impl From<Offset> for config::Offset { + fn from(o: Offset) -> Self { + match o { + Offset::Begin(i) => config::Offset::Begin(i), + Offset::End(i) => config::Offset::End(i), + } + } +} diff --git a/vendor/tabled/src/settings/style/raw_style.rs b/vendor/tabled/src/settings/style/raw_style.rs new file mode 100644 index 000000000..eedb48e79 --- /dev/null +++ b/vendor/tabled/src/settings/style/raw_style.rs @@ -0,0 +1,438 @@ +//! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic, +//! so sometimes it can be used more conviently. + +use std::collections::HashMap; + +use crate::{ + grid::{color::AnsiColor, config, config::Borders, config::ColoredConfig, records::Records}, + settings::{Color, TableOption}, +}; + +use super::{Border, HorizontalLine, Line, Style, VerticalLine}; + +/// A raw style data, which can be produced safely from [`Style`]. +/// +/// It can be useful in order to not have a generics and be able to use it as a variable more conveniently. +#[derive(Default, Debug, Clone)] +pub struct RawStyle { + borders: Borders<char>, + colors: Borders<AnsiColor<'static>>, + horizontals: HashMap<usize, Line>, + verticals: HashMap<usize, Line>, +} + +impl RawStyle { + /// Set a top border character. + pub fn set_top(&mut self, s: Option<char>) -> &mut Self { + self.borders.top = s; + self + } + + /// Set a top border color. + pub fn set_color_top(&mut self, color: Color) -> &mut Self { + self.colors.top = Some(color.into()); + self + } + + /// Set a bottom border character. + pub fn set_bottom(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom = s; + self + } + + /// Set a bottom border color. + pub fn set_color_bottom(&mut self, color: Color) -> &mut Self { + self.colors.bottom = Some(color.into()); + self + } + + /// Set a left border character. + pub fn set_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.left = s; + self + } + + /// Set a left border color. + pub fn set_color_left(&mut self, color: Color) -> &mut Self { + self.colors.left = Some(color.into()); + self + } + + /// Set a right border character. + pub fn set_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.right = s; + self + } + + /// Set a right border color. + pub fn set_color_right(&mut self, color: Color) -> &mut Self { + self.colors.right = Some(color.into()); + self + } + + /// Set a top intersection character. + pub fn set_intersection_top(&mut self, s: Option<char>) -> &mut Self { + self.borders.top_intersection = s; + self + } + + /// Set a top intersection color. + pub fn set_color_intersection_top(&mut self, color: Color) -> &mut Self { + self.colors.top_intersection = Some(color.into()); + self + } + + /// Set a bottom intersection character. + pub fn set_intersection_bottom(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_bottom(&mut self, color: Color) -> &mut Self { + self.colors.bottom_intersection = Some(color.into()); + self + } + + /// Set a left split character. + pub fn set_intersection_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.left_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_left(&mut self, color: Color) -> &mut Self { + self.colors.left_intersection = Some(color.into()); + self + } + + /// Set a right split character. + pub fn set_intersection_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.right_intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection_right(&mut self, color: Color) -> &mut Self { + self.colors.right_intersection = Some(color.into()); + self + } + + /// Set an internal character. + pub fn set_intersection(&mut self, s: Option<char>) -> &mut Self { + self.borders.intersection = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_intersection(&mut self, color: Color) -> &mut Self { + self.colors.intersection = Some(color.into()); + self + } + + /// Set a vertical character. + pub fn set_vertical(&mut self, s: Option<char>) -> &mut Self { + self.borders.vertical = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_vertical(&mut self, color: Color) -> &mut Self { + self.colors.vertical = Some(color.into()); + self + } + + /// Set a horizontal character. + pub fn set_horizontal(&mut self, s: Option<char>) -> &mut Self { + self.borders.horizontal = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_horizontal(&mut self, color: Color) -> &mut Self { + self.colors.horizontal = Some(color.into()); + self + } + + /// Set a character for a top left corner. + pub fn set_corner_top_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.top_left = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_top_left(&mut self, color: Color) -> &mut Self { + self.colors.top_left = Some(color.into()); + self + } + + /// Set a character for a top right corner. + pub fn set_corner_top_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.top_right = s; + self + } + + /// Set a bottom intersection color. + pub fn set_color_corner_top_right(&mut self, color: Color) -> &mut Self { + self.colors.top_right = Some(color.into()); + self + } + + /// Set a character for a bottom left corner. + pub fn set_corner_bottom_left(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom_left = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_bottom_left(&mut self, color: Color) -> &mut Self { + self.colors.bottom_left = Some(color.into()); + self + } + + /// Set a character for a bottom right corner. + pub fn set_corner_bottom_right(&mut self, s: Option<char>) -> &mut Self { + self.borders.bottom_right = s; + self + } + /// Set a bottom intersection color. + pub fn set_color_corner_bottom_right(&mut self, color: Color) -> &mut Self { + self.colors.bottom_right = Some(color.into()); + self + } + + /// Set horizontal border lines. + /// + /// # Example + /// + /// ``` + /// use std::collections::HashMap; + /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; + /// + /// let mut style = RawStyle::from(Style::re_structured_text()); + /// + /// let mut lines = HashMap::new(); + /// lines.insert(1, Style::extended().get_horizontal()); + /// style.set_horizontals(lines); + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(style) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// " ======= ===== \n", + /// " &str i32 \n", + /// "╠═══════╬═════╣\n", + /// " Hello 0 \n", + /// " Hello 1 \n", + /// " Hello 2 \n", + /// " ======= ===== ", + /// ), + /// ) + /// ``` + pub fn set_horizontals(&mut self, lines: HashMap<usize, Line>) -> &mut Self { + self.horizontals = lines; + self + } + + /// Insert a horizontal line to a specific row location. + pub fn insert_horizontal(&mut self, row: usize, line: Line) -> &mut Self { + let _ = self.horizontals.insert(row, line); + self + } + + /// Insert a horizontal line to a specific row location. + pub fn get_horizontal(&self, row: usize) -> Option<Line> { + self.horizontals.get(&row).cloned() + } + + /// Set vertical border lines. + /// + /// # Example + /// + /// ``` + /// use std::collections::HashMap; + /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; + /// + /// let mut style = RawStyle::from(Style::re_structured_text()); + /// + /// let mut lines = HashMap::new(); + /// lines.insert(1, Style::extended().get_horizontal()); + /// style.set_verticals(lines); + /// + /// let table = Table::new((0..3).map(|i| ("Hello", i))) + /// .with(style) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// concat!( + /// "=======╠=====\n", + /// " &str ═ i32 \n", + /// "======= =====\n", + /// " Hello ═ 0 \n", + /// " Hello ═ 1 \n", + /// " Hello ═ 2 \n", + /// "=======╣=====", + /// ), + /// ) + /// ``` + pub fn set_verticals(&mut self, lines: HashMap<usize, Line>) -> &mut Self { + self.verticals = lines; + self + } + + /// Insert a vertical line into specific column location. + pub fn insert_vertical(&mut self, column: usize, line: Line) -> &mut Self { + let _ = self.verticals.insert(column, line); + self + } + + /// Get a left char. + pub fn get_left(&self) -> Option<char> { + self.borders.left + } + + /// Get a left intersection char. + pub fn get_left_intersection(&self) -> Option<char> { + self.borders.left_intersection + } + + /// Get a right char. + pub fn get_right(&self) -> Option<char> { + self.borders.right + } + + /// Get a right intersection char. + pub fn get_right_intersection(&self) -> Option<char> { + self.borders.right_intersection + } + + /// Get a top char. + pub fn get_top(&self) -> Option<char> { + self.borders.top + } + + /// Get a top left char. + pub fn get_top_left(&self) -> Option<char> { + self.borders.top_left + } + + /// Get a top right char. + pub fn get_top_right(&self) -> Option<char> { + self.borders.top_right + } + + /// Get a top intersection char. + pub fn get_top_intersection(&self) -> Option<char> { + self.borders.top_intersection + } + + /// Get a bottom intersection char. + pub fn get_bottom(&self) -> Option<char> { + self.borders.bottom + } + + /// Get a bottom intersection char. + pub fn get_bottom_left(&self) -> Option<char> { + self.borders.bottom_left + } + + /// Get a bottom intersection char. + pub fn get_bottom_right(&self) -> Option<char> { + self.borders.bottom_right + } + + /// Get a bottom intersection char. + pub fn get_bottom_intersection(&self) -> Option<char> { + self.borders.bottom_intersection + } + + /// Returns an outer border of the style. + pub fn get_frame(&self) -> Border { + Border::from(crate::grid::config::Border { + top: self.borders.top, + bottom: self.borders.bottom, + left: self.borders.left, + right: self.borders.right, + left_top_corner: self.borders.top_left, + right_top_corner: self.borders.top_right, + left_bottom_corner: self.borders.bottom_left, + right_bottom_corner: self.borders.bottom_right, + }) + } + + /// Returns an general borders configuration of the style. + pub fn get_borders(&self) -> Borders<char> { + self.borders + } +} + +impl From<Borders<char>> for RawStyle { + fn from(borders: Borders<char>) -> Self { + Self { + borders, + horizontals: HashMap::new(), + verticals: HashMap::new(), + colors: Borders::default(), + } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for RawStyle +where + R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { + (&self).change(records, cfg, dimension) + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for &RawStyle { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.clear_theme(); + + cfg.set_borders(self.borders); + + for (&row, line) in &self.horizontals { + cfg.insert_horizontal_line(row, config::HorizontalLine::from(*line)); + } + + for (&col, line) in &self.verticals { + cfg.insert_vertical_line(col, config::VerticalLine::from(*line)); + } + + if !self.colors.is_empty() { + cfg.set_borders_color(self.colors.clone()); + } + } +} + +impl<T, B, L, R, H, V, HLines, VLines> From<Style<T, B, L, R, H, V, HLines, VLines>> for RawStyle +where + HLines: IntoIterator<Item = HorizontalLine> + Clone, + VLines: IntoIterator<Item = VerticalLine> + Clone, +{ + fn from(style: Style<T, B, L, R, H, V, HLines, VLines>) -> Self { + let horizontals = style + .get_horizontals() + .clone() + .into_iter() + .map(|hr| (hr.index, hr.line)) + .collect(); + + let verticals = style + .get_verticals() + .clone() + .into_iter() + .map(|hr| (hr.index, hr.line)) + .collect(); + + Self { + borders: *style.get_borders(), + horizontals, + verticals, + colors: Borders::default(), + } + } +} diff --git a/vendor/tabled/src/settings/style/span_border_correction.rs b/vendor/tabled/src/settings/style/span_border_correction.rs new file mode 100644 index 000000000..665a12788 --- /dev/null +++ b/vendor/tabled/src/settings/style/span_border_correction.rs @@ -0,0 +1,216 @@ +//! This module contains [`BorderSpanCorrection`] structure, which can be useful when [`Span`] is used, and +//! you want to fix the intersections symbols which are left intact by default. +//! +//! [`Span`]: crate::settings::span::Span + +use crate::{ + grid::{ + config::{ColoredConfig, Position, SpannedConfig}, + records::{ExactRecords, Records}, + }, + settings::TableOption, +}; + +/// A correctness function of style for [`Table`] which has [`Span`]s. +/// +/// Try to fix the style when table contains spans. +/// +/// By default [`Style`] doesn't implies any logic to better render split lines when +/// [`Span`] is used. +/// +/// So this function can be used to set the split lines in regard of spans used. +/// +/// # Example +/// +/// ``` +/// use tabled::{ +/// Table, +/// settings::{ +/// Modify, style::{Style, BorderSpanCorrection}, +/// Format, Span, object::Cell +/// } +/// }; +/// +/// let data = vec![ +/// ("09", "June", "2022"), +/// ("10", "July", "2022"), +/// ]; +/// +/// let mut table = Table::new(data); +/// table.with(Modify::new((0, 0)).with("date").with(Span::column(3))); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "+----+------+------+\n", +/// "| date |\n", +/// "+----+------+------+\n", +/// "| 09 | June | 2022 |\n", +/// "+----+------+------+\n", +/// "| 10 | July | 2022 |\n", +/// "+----+------+------+", +/// ) +/// ); +/// +/// table.with(BorderSpanCorrection); +/// +/// assert_eq!( +/// table.to_string(), +/// concat!( +/// "+------------------+\n", +/// "| date |\n", +/// "+----+------+------+\n", +/// "| 09 | June | 2022 |\n", +/// "+----+------+------+\n", +/// "| 10 | July | 2022 |\n", +/// "+----+------+------+", +/// ) +/// ); +/// ``` +/// See [`BorderSpanCorrection`]. +/// +/// [`Table`]: crate::Table +/// [`Span`]: crate::settings::span::Span +/// [`Style`]: crate::settings::Style +/// [`Style::correct_spans`]: crate::settings::style::BorderSpanCorrection +#[derive(Debug)] +pub struct BorderSpanCorrection; + +impl<R, D> TableOption<R, D, ColoredConfig> for BorderSpanCorrection +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let shape = (records.count_rows(), records.count_columns()); + correct_span_styles(cfg, shape); + } +} + +fn correct_span_styles(cfg: &mut SpannedConfig, shape: (usize, usize)) { + for ((row, c), span) in cfg.get_column_spans() { + for col in c..c + span { + if col == 0 { + continue; + } + + let is_first = col == c; + let has_up = row > 0 && has_left(cfg, (row - 1, col), shape); + let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); + + let borders = cfg.get_borders(); + + let mut border = cfg.get_border((row, col), shape); + + let has_top_border = border.left_top_corner.is_some() && border.top.is_some(); + if has_top_border { + if has_up && is_first { + border.left_top_corner = borders.intersection; + } else if has_up { + border.left_top_corner = borders.bottom_intersection; + } else if is_first { + border.left_top_corner = borders.top_intersection; + } else { + border.left_top_corner = border.top; + } + } + + let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some(); + if has_bottom_border { + if has_down && is_first { + border.left_bottom_corner = borders.intersection; + } else if has_down { + border.left_bottom_corner = borders.top_intersection; + } else if is_first { + border.left_bottom_corner = borders.bottom_intersection; + } else { + border.left_bottom_corner = border.bottom; + } + } + + cfg.set_border((row, col), border); + } + } + + for ((r, col), span) in cfg.get_row_spans() { + for row in r + 1..r + span { + let mut border = cfg.get_border((row, col), shape); + let borders = cfg.get_borders(); + + let has_left_border = border.left_top_corner.is_some(); + if has_left_border { + let has_left = col > 0 && has_top(cfg, (row, col - 1), shape); + if has_left { + border.left_top_corner = borders.right_intersection; + } else { + border.left_top_corner = borders.vertical; + } + } + + let has_right_border = border.right_top_corner.is_some(); + if has_right_border { + let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); + if has_right { + border.right_top_corner = borders.left_intersection; + } else { + border.right_top_corner = borders.vertical; + } + } + + cfg.set_border((row, col), border); + } + } + + let cells = iter_totaly_spanned_cells(cfg, shape).collect::<Vec<_>>(); + for (row, col) in cells { + if row == 0 { + continue; + } + + let mut border = cfg.get_border((row, col), shape); + let borders = cfg.get_borders(); + + let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); + let has_up = has_left(cfg, (row - 1, col), shape); + if has_up && !has_right { + border.right_top_corner = borders.right_intersection; + } + + let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); + if has_down { + border.left_bottom_corner = borders.top_intersection; + } + + cfg.set_border((row, col), border); + } +} + +fn has_left(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { + if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_column_span(pos) { + return false; + } + + let border = cfg.get_border(pos, shape); + border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some() +} + +fn has_top(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { + if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_row_span(pos) { + return false; + } + + let border = cfg.get_border(pos, shape); + border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some() +} + +fn iter_totaly_spanned_cells( + cfg: &SpannedConfig, + shape: (usize, usize), +) -> impl Iterator<Item = Position> + '_ { + // todo: can be optimized + let (count_rows, count_cols) = shape; + (0..count_rows).flat_map(move |row| { + (0..count_cols) + .map(move |col| (row, col)) + .filter(move |&p| cfg.is_cell_covered_by_both_spans(p)) + }) +} diff --git a/vendor/tabled/src/settings/style/vertical_line.rs b/vendor/tabled/src/settings/style/vertical_line.rs new file mode 100644 index 000000000..adbef2c82 --- /dev/null +++ b/vendor/tabled/src/settings/style/vertical_line.rs @@ -0,0 +1,50 @@ +#[cfg(feature = "std")] +use crate::grid::config::{ColoredConfig, VerticalLine as VLine}; + +use super::Line; + +/// A horizontal split line which can be used to set a border. +#[cfg_attr(not(feature = "std"), allow(dead_code))] +#[derive(Debug, Clone)] +pub struct VerticalLine { + pub(crate) index: usize, + pub(crate) line: Line, +} + +impl VerticalLine { + /// Creates a new horizontal split line. + pub const fn new(index: usize, line: Line) -> Self { + Self { index, line } + } + + /// Sets a horizontal character. + pub const fn main(mut self, c: Option<char>) -> Self { + self.line.main = c; + self + } + + /// Sets a vertical intersection character. + pub const fn intersection(mut self, c: Option<char>) -> Self { + self.line.intersection = c; + self + } + + /// Sets a top character. + pub const fn top(mut self, c: Option<char>) -> Self { + self.line.connector1 = c; + self + } + + /// Sets a bottom character. + pub const fn bottom(mut self, c: Option<char>) -> Self { + self.line.connector2 = c; + self + } +} + +#[cfg(feature = "std")] +impl<R, D> crate::settings::TableOption<R, D, ColoredConfig> for VerticalLine { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + cfg.insert_vertical_line(self.index, VLine::from(self.line)); + } +} diff --git a/vendor/tabled/src/settings/table_option.rs b/vendor/tabled/src/settings/table_option.rs new file mode 100644 index 000000000..e20aa4480 --- /dev/null +++ b/vendor/tabled/src/settings/table_option.rs @@ -0,0 +1,30 @@ +/// A trait which is responsible for configuration of a [`Table`]. +/// +/// [`Table`]: crate::Table +pub trait TableOption<R, D, C> { + /// The function allows modification of records and a grid configuration. + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D); +} + +impl<T, R, D, C> TableOption<R, D, C> for &[T] +where + for<'a> &'a T: TableOption<R, D, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { + for opt in self { + opt.change(records, cfg, dimension) + } + } +} + +#[cfg(feature = "std")] +impl<T, R, D, C> TableOption<R, D, C> for Vec<T> +where + T: TableOption<R, D, C>, +{ + fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { + for opt in self { + opt.change(records, cfg, dimension) + } + } +} diff --git a/vendor/tabled/src/settings/themes/colorization.rs b/vendor/tabled/src/settings/themes/colorization.rs new file mode 100644 index 000000000..41c721330 --- /dev/null +++ b/vendor/tabled/src/settings/themes/colorization.rs @@ -0,0 +1,388 @@ +use papergrid::{ + color::AnsiColor, + config::{Entity, Sides}, +}; + +use crate::{ + grid::{ + config::ColoredConfig, + records::{ExactRecords, Records}, + }, + settings::{object::Object, Color, TableOption}, +}; + +/// [`Colorization`] sets a color for the whole table data (so it's not include the borders). +/// +/// You can colorize borders in a different round using [`BorderColor`] or [`RawStyle`] +/// +/// # Examples +/// +/// ``` +/// use std::iter::FromIterator; +/// +/// use tabled::builder::Builder; +/// use tabled::settings::{style::BorderColor, themes::Colorization, Color, Style}; +/// +/// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; +/// +/// let color1 = Color::FG_BLACK | Color::BG_WHITE; +/// let color2 = Color::BG_BLACK | Color::FG_WHITE; +/// let color3 = Color::FG_RED | Color::BG_RED; +/// +/// let mut table = Builder::from_iter(data).build(); +/// table +/// .with(Colorization::chess(color1, color2)) +/// .with(Style::modern()) +/// .with(BorderColor::filled(color3)); +/// +/// println!("{table}"); +/// ``` +/// +/// [`RawStyle`]: crate::settings::style::RawStyle +/// [`BorderColor`]: crate::settings::style::BorderColor +#[derive(Debug, Clone)] +pub struct Colorization { + pattern: ColorizationPattern, + colors: Vec<Color>, +} + +#[derive(Debug, Clone)] +enum ColorizationPattern { + Column, + Row, + ByRow, + ByColumn, + Chess, +} + +impl Colorization { + /// Creates a [`Colorization`] with a chess pattern. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::chess(color1, color2)) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn chess(white: Color, black: Color) -> Self { + Self::new(vec![white, black], ColorizationPattern::Chess) + } + + /// Creates a [`Colorization`] with a target [`Object`]. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::exact([color1, color2], Rows::first())) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn exact<I, O>(colors: I, target: O) -> ExactColorization<O> + where + I: IntoIterator, + I::Item: Into<Color>, + { + let colors = colors.into_iter().map(Into::into).collect(); + ExactColorization::new(colors, target) + } + + /// Creates a [`Colorization`] with a pattern which changes row by row. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::rows([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn rows<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::Row) + } + + /// Creates a [`Colorization`] with a pattern which changes column by column. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::columns([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn columns<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::Column) + } + + /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over rows. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::by_row([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn by_row<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::ByRow) + } + + /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over columns. + /// + /// ``` + /// use std::iter::FromIterator; + /// + /// use tabled::builder::Builder; + /// use tabled::settings::object::Rows; + /// use tabled::settings::{themes::Colorization, Color, Style}; + /// + /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; + /// + /// let color1 = Color::FG_BLACK | Color::BG_WHITE; + /// let color2 = Color::BG_BLACK | Color::FG_WHITE; + /// + /// let mut table = Builder::from_iter(data).build(); + /// table + /// .with(Colorization::by_column([color1, color2])) + /// .with(Style::empty()); + /// + /// println!("{table}"); + /// ``` + pub fn by_column<I>(colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + Self::new(colors, ColorizationPattern::ByColumn) + } + + fn new<I>(colors: I, pattern: ColorizationPattern) -> Self + where + I: IntoIterator, + I::Item: Into<Color>, + { + let colors = colors.into_iter().map(Into::into).collect(); + Self { colors, pattern } + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for Colorization +where + R: Records + ExactRecords, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + if self.colors.is_empty() { + return; + } + + let count_columns = records.count_columns(); + let count_rows = records.count_rows(); + + match self.pattern { + ColorizationPattern::Column => colorize_columns(&self.colors, count_columns, cfg), + ColorizationPattern::Row => colorize_rows(&self.colors, count_rows, cfg), + ColorizationPattern::ByRow => { + colorize_by_row(&self.colors, count_rows, count_columns, cfg) + } + ColorizationPattern::ByColumn => { + colorize_by_column(&self.colors, count_rows, count_columns, cfg) + } + ColorizationPattern::Chess => { + colorize_diogonals(&self.colors, count_rows, count_columns, cfg) + } + } + } +} + +fn colorize_columns(colors: &[Color], count_columns: usize, cfg: &mut ColoredConfig) { + for (col, color) in (0..count_columns).zip(colors.iter().cycle()) { + colorize_entity(color, Entity::Column(col), cfg); + } +} + +fn colorize_rows(colors: &[Color], count_rows: usize, cfg: &mut ColoredConfig) { + for (row, color) in (0..count_rows).zip(colors.iter().cycle()) { + colorize_entity(color, Entity::Row(row), cfg); + } +} + +fn colorize_by_row( + colors: &[Color], + count_rows: usize, + count_columns: usize, + cfg: &mut ColoredConfig, +) { + let mut color_peek = colors.iter().cycle(); + for row in 0..count_rows { + for col in 0..count_columns { + let color = color_peek.next().unwrap(); + colorize_entity(color, Entity::Cell(row, col), cfg); + } + } +} + +fn colorize_by_column( + colors: &[Color], + count_rows: usize, + count_columns: usize, + cfg: &mut ColoredConfig, +) { + let mut color_peek = colors.iter().cycle(); + for col in 0..count_columns { + for row in 0..count_rows { + let color = color_peek.next().unwrap(); + colorize_entity(color, Entity::Cell(row, col), cfg); + } + } +} + +fn colorize_diogonals( + colors: &[Color], + count_rows: usize, + count_columns: usize, + cfg: &mut ColoredConfig, +) { + let mut color_peek = colors.iter().cycle(); + for mut row in 0..count_rows { + let color = color_peek.next().unwrap(); + for col in 0..count_columns { + colorize_entity(color, Entity::Cell(row, col), cfg); + + row += 1; + if row == count_rows { + break; + } + } + } + + let _ = color_peek.next().unwrap(); + + for mut col in 1..count_columns { + let color = color_peek.next().unwrap(); + for row in 0..count_rows { + colorize_entity(color, Entity::Cell(row, col), cfg); + + col += 1; + if col == count_columns { + break; + } + } + } +} + +fn colorize_entity(color: &Color, pos: Entity, cfg: &mut ColoredConfig) { + let ansi_color = AnsiColor::from(color.clone()); + let _ = cfg.set_color(pos, ansi_color.clone()); + cfg.set_justification_color(pos, Some(ansi_color.clone())); + cfg.set_padding_color( + pos, + Sides::new( + Some(ansi_color.clone()), + Some(ansi_color.clone()), + Some(ansi_color.clone()), + Some(ansi_color), + ), + ); +} + +/// A colorization of a target [`Object`]. +/// +/// Can be created by [`Colorization::exact`]. +#[derive(Debug, Clone)] +pub struct ExactColorization<O> { + colors: Vec<Color>, + target: O, +} + +impl<O> ExactColorization<O> { + fn new(colors: Vec<Color>, target: O) -> Self { + Self { colors, target } + } +} + +impl<R, D, O> TableOption<R, D, ColoredConfig> for ExactColorization<O> +where + O: Object<R>, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + if self.colors.is_empty() { + return; + } + + let mut color_peek = self.colors.iter().cycle(); + for pos in self.target.cells(records) { + let color = color_peek.next().unwrap(); + colorize_entity(color, pos, cfg); + } + } +} diff --git a/vendor/tabled/src/settings/themes/column_names.rs b/vendor/tabled/src/settings/themes/column_names.rs new file mode 100644 index 000000000..02848166d --- /dev/null +++ b/vendor/tabled/src/settings/themes/column_names.rs @@ -0,0 +1,355 @@ +use crate::{ + grid::{ + config::{AlignmentHorizontal, ColoredConfig}, + dimension::{CompleteDimensionVecRecords, Dimension, Estimate}, + records::{ + vec_records::{CellInfo, VecRecords}, + ExactRecords, PeekableRecords, Records, Resizable, + }, + util::string::string_width, + }, + settings::{ + style::{BorderText, Offset}, + Color, TableOption, + }, +}; + +/// [`ColumnNames`] sets strings on horizontal lines for the columns. +/// +/// Notice that using a [`Default`] would reuse a names from the first row. +/// +/// # Examples +/// +/// ``` +/// use std::iter::FromIterator; +/// use tabled::{Table, settings::themes::ColumnNames}; +/// +/// let data = vec![ +/// vec!["Hello", "World"], +/// vec!["Hello", "World"], +/// ]; +/// +/// let mut table = Table::from_iter(data); +/// table.with(ColumnNames::new(["head1", "head2"]).set_offset(3).set_line(2)); +/// +/// assert_eq!( +/// table.to_string(), +/// "+--------+--------+\n\ +/// | Hello | World |\n\ +/// +--------+--------+\n\ +/// | Hello | World |\n\ +/// +---head1+---head2+" +/// ); +/// ``` +/// +/// [`Default`] usage. +/// +/// ``` +/// use std::iter::FromIterator; +/// use tabled::{Table, settings::themes::ColumnNames}; +/// +/// let data = vec![ +/// vec!["Hello", "World"], +/// vec!["Hello", "World"], +/// ]; +/// +/// let mut table = Table::from_iter(data); +/// table.with(ColumnNames::default()); +/// +/// assert_eq!( +/// table.to_string(), +/// "+Hello--+World--+\n\ +/// | Hello | World |\n\ +/// +-------+-------+" +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct ColumnNames { + names: Option<Vec<String>>, + colors: Vec<Option<Color>>, + offset: usize, + line: usize, + alignment: AlignmentHorizontal, +} + +impl Default for ColumnNames { + fn default() -> Self { + Self { + names: Default::default(), + colors: Default::default(), + offset: Default::default(), + line: Default::default(), + alignment: AlignmentHorizontal::Left, + } + } +} + +impl ColumnNames { + /// Creates a [`ColumnNames`] with a given names. + /// + /// Using a [`Default`] would reuse a names from the first row. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{Table, settings::themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"])); + /// + /// assert_eq!( + /// table.to_string(), + /// "+head1--+head2--+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn new<I>(names: I) -> Self + where + I: IntoIterator, + I::Item: Into<String>, + { + let names = names.into_iter().map(Into::into).collect::<Vec<_>>(); + Self { + names: Some(names), + colors: Vec::new(), + offset: 0, + line: 0, + alignment: AlignmentHorizontal::Left, + } + } + + /// Set color for the column names. + /// + /// By default there's no colors. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::Table; + /// use tabled::settings::{Color, themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_colors([Color::FG_RED])); + /// + /// assert_eq!( + /// table.to_string(), + /// "+\u{1b}[31mh\u{1b}[39m\u{1b}[31me\u{1b}[39m\u{1b}[31ma\u{1b}[39m\u{1b}[31md\u{1b}[39m\u{1b}[31m1\u{1b}[39m--+head2--+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn set_colors<I>(self, colors: I) -> Self + where + I: IntoIterator, + I::Item: Into<Option<Color>>, + { + let colors = colors.into_iter().map(Into::into).collect::<Vec<_>>(); + Self { + names: self.names, + offset: self.offset, + line: self.line, + alignment: self.alignment, + colors, + } + } + + /// Set a left offset after which the names will be used. + /// + /// By default there's no offset. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{Table, settings::themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_offset(1)); + /// + /// assert_eq!( + /// table.to_string(), + /// "+-head1-+-head2-+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn set_offset(self, i: usize) -> Self { + Self { + names: self.names, + colors: self.colors, + line: self.line, + alignment: self.alignment, + offset: i, + } + } + + /// Set a horizontal line the names will be applied to. + /// + /// The default value is 0 (the top horizontal line). + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{Table, settings::themes::ColumnNames}; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_line(1)); + /// + /// assert_eq!( + /// table.to_string(), + /// "+-------+-------+\n\ + /// | Hello | World |\n\ + /// +head1--+head2--+" + /// ); + /// ``` + pub fn set_line(self, i: usize) -> Self { + Self { + names: self.names, + colors: self.colors, + offset: self.offset, + alignment: self.alignment, + line: i, + } + } + + /// Set an alignment for the names. + /// + /// By default it's left aligned. + /// + /// # Example + /// + /// ``` + /// use std::iter::FromIterator; + /// use tabled::{ + /// Table, + /// settings::themes::ColumnNames, + /// grid::config::AlignmentHorizontal, + /// }; + /// + /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); + /// table.with(ColumnNames::new(["head1", "head2"]).set_alignment(AlignmentHorizontal::Right)); + /// + /// assert_eq!( + /// table.to_string(), + /// "+--head1+--head2+\n\ + /// | Hello | World |\n\ + /// +-------+-------+" + /// ); + /// ``` + pub fn set_alignment(self, alignment: AlignmentHorizontal) -> Self { + Self { + names: self.names, + colors: self.colors, + offset: self.offset, + line: self.line, + alignment, + } + } +} + +impl TableOption<VecRecords<CellInfo<String>>, CompleteDimensionVecRecords<'static>, ColoredConfig> + for ColumnNames +{ + fn change( + self, + records: &mut VecRecords<CellInfo<String>>, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + let names = match self.names { + Some(names) => names, + None => { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let names = (0..records.count_columns()) + .map(|column| records.get_text((0, column))) + .map(ToString::to_string) + .collect::<Vec<_>>(); + + records.remove_row(0); + + names + } + }; + + let names = names.iter().map(|name| name.lines().next().unwrap_or("")); + + dims.estimate(&*records, cfg); + + let mut widths = (0..records.count_columns()) + .map(|column| dims.get_width(column)) + .collect::<Vec<_>>(); + + let names = names.take(widths.len()); + + let offset = self.offset; + widths + .iter_mut() + .zip(names.clone()) + .for_each(|(width, text)| { + let name_width = string_width(text) + offset; + *width = std::cmp::max(name_width, *width); + }); + + let _ = dims.set_widths(widths.clone()); + + let mut total_width = 0; + for (i, (width, name)) in widths.iter().zip(names).enumerate() { + let color = get_color(&self.colors, i); + let offset = total_width + 1; + let btext = get_border_text( + name, + offset, + *width, + self.alignment, + self.offset, + self.line, + color, + ); + btext.change(records, cfg, dims); + + total_width += width + 1; + } + } +} + +fn get_border_text( + text: &str, + offset: usize, + available: usize, + alignment: AlignmentHorizontal, + alignment_offset: usize, + line: usize, + color: Option<&Color>, +) -> BorderText<usize> { + let name = text.to_string(); + let left_indent = get_indent(text, alignment, alignment_offset, available); + let left_offset = Offset::Begin(offset + left_indent); + let mut btext = BorderText::new(name).horizontal(line).offset(left_offset); + if let Some(color) = color { + btext = btext.color(color.clone()); + } + + btext +} + +fn get_color(colors: &[Option<Color>], i: usize) -> Option<&Color> { + colors.get(i).and_then(|color| match color { + Some(color) => Some(color), + None => None, + }) +} + +fn get_indent(text: &str, align: AlignmentHorizontal, offset: usize, available: usize) -> usize { + match align { + AlignmentHorizontal::Left => offset, + AlignmentHorizontal::Right => available - string_width(text) - offset, + AlignmentHorizontal::Center => (available - string_width(text)) / 2, + } +} diff --git a/vendor/tabled/src/settings/themes/mod.rs b/vendor/tabled/src/settings/themes/mod.rs new file mode 100644 index 000000000..75cf458cc --- /dev/null +++ b/vendor/tabled/src/settings/themes/mod.rs @@ -0,0 +1,9 @@ +//! The module contains a varieity of configurations of table, which often +//! changes not a single setting. +//! As such they are making relatively big changes to the configuration. + +mod colorization; +mod column_names; + +pub use colorization::{Colorization, ExactColorization}; +pub use column_names::ColumnNames; diff --git a/vendor/tabled/src/settings/width/justify.rs b/vendor/tabled/src/settings/width/justify.rs new file mode 100644 index 000000000..03b2afe0d --- /dev/null +++ b/vendor/tabled/src/settings/width/justify.rs @@ -0,0 +1,96 @@ +//! This module contains [`Justify`] structure, used to set an exact width to each column. + +use crate::{ + grid::config::ColoredConfig, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + settings::{ + measurement::{Max, Measurement, Min}, + CellOption, TableOption, Width, + }, +}; + +/// Justify sets all columns widths to the set value. +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Examples +/// +/// ``` +/// use tabled::{Table, settings::{Width, Style, object::Segment, Padding, Modify}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Modify::new(Segment::all()).with(Padding::zero())) +/// .with(Width::justify(3)); +/// ``` +/// +/// [`Max`] usage to justify by a max column width. +/// +/// ``` +/// use tabled::{Table, settings::{width::Justify, Style}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Justify::max()); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Justify<W> { + width: W, +} + +impl<W> Justify<W> +where + W: Measurement<Width>, +{ + /// Creates a new [`Justify`] instance. + /// + /// Be aware that [`Padding`] is not considered when comparing the width. + /// + /// [`Padding`]: crate::settings::Padding + pub fn new(width: W) -> Self { + Self { width } + } +} + +impl Justify<Max> { + /// Creates a new Justify instance with a Max width used as a value. + pub fn max() -> Self { + Self { width: Max } + } +} + +impl Justify<Min> { + /// Creates a new Justify instance with a Min width used as a value. + pub fn min() -> Self { + Self { width: Min } + } +} + +impl<R, D, W> TableOption<R, D, ColoredConfig> for Justify<W> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let width = self.width.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for row in 0..count_rows { + for col in 0..count_columns { + let pos = (row, col).into(); + CellOption::change(Width::increase(width), records, cfg, pos); + CellOption::change(Width::truncate(width), records, cfg, pos); + } + } + } +} diff --git a/vendor/tabled/src/settings/width/min_width.rs b/vendor/tabled/src/settings/width/min_width.rs new file mode 100644 index 000000000..afb9ae302 --- /dev/null +++ b/vendor/tabled/src/settings/width/min_width.rs @@ -0,0 +1,205 @@ +//! This module contains [`MinWidth`] structure, used to increase width of a [`Table`]s or a cell on a [`Table`]. +//! +//! [`Table`]: crate::Table + +use std::marker::PhantomData; + +use crate::{ + grid::config::ColoredConfig, + grid::config::Entity, + grid::dimension::CompleteDimensionVecRecords, + grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, + grid::util::string::{get_lines, string_width_multiline}, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + CellOption, TableOption, Width, + }, +}; + +use super::util::get_table_widths_with_total; + +/// [`MinWidth`] changes a content in case if it's length is lower then the boundary. +/// +/// It can be applied to a whole table. +/// +/// It does nothing in case if the content's length is bigger then the boundary. +/// +/// Be aware that further changes of the table may cause the width being not set. +/// For example applying [`Padding`] after applying [`MinWidth`] will make the former have no affect. +/// (You should use [`Padding`] first). +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Examples +/// +/// Cell change +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Modify::new(Segment::all()).with(Width::increase(10))); +/// ``` +/// Table change +/// +/// ``` +/// use tabled::{Table, settings::Width}; +/// +/// let table = Table::new(&["Hello World!"]).with(Width::increase(5)); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct MinWidth<W = usize, P = PriorityNone> { + width: W, + fill: char, + _priority: PhantomData<P>, +} + +impl<W> MinWidth<W> +where + W: Measurement<Width>, +{ + /// Creates a new instance of [`MinWidth`]. + pub fn new(width: W) -> Self { + Self { + width, + fill: ' ', + _priority: PhantomData, + } + } +} + +impl<W, P> MinWidth<W, P> { + /// Set's a fill character which will be used to fill the space + /// when increasing the length of the string to the set boundary. + /// + /// Used only if chaning cells. + pub fn fill_with(mut self, c: char) -> Self { + self.fill = c; + self + } + + /// Priority defines the logic by which a increase of width will be applied when is done for the whole table. + /// + /// - [`PriorityNone`] which inc the columns one after another. + /// - [`PriorityMax`] inc the biggest columns first. + /// - [`PriorityMin`] inc the lowest columns first. + /// + /// [`PriorityMax`]: crate::settings::peaker::PriorityMax + /// [`PriorityMin`]: crate::settings::peaker::PriorityMin + pub fn priority<PP: Peaker>(self) -> MinWidth<W, PP> { + MinWidth { + fill: self.fill, + width: self.width, + _priority: PhantomData, + } + } +} + +impl<W, R> CellOption<R, ColoredConfig> for MinWidth<W> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let width = self.width.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let cell = records.get_text(pos); + let cell_width = string_width_multiline(cell); + if cell_width >= width { + continue; + } + + let content = increase_width(cell, width, self.fill); + records.set(pos, content); + } + } +} + +impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for MinWidth<W, P> +where + W: Measurement<Width>, + P: Peaker, + R: Records + ExactRecords + PeekableRecords, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let nessary_width = self.width.measure(&*records, cfg); + + let (widths, total_width) = get_table_widths_with_total(&*records, cfg); + if total_width >= nessary_width { + return; + } + + let widths = get_increase_list(widths, nessary_width, total_width, P::create()); + let _ = dims.set_widths(widths); + } +} + +fn get_increase_list<F>( + mut widths: Vec<usize>, + need: usize, + mut current: usize, + mut peaker: F, +) -> Vec<usize> +where + F: Peaker, +{ + while need != current { + let col = match peaker.peak(&[], &widths) { + Some(col) => col, + None => break, + }; + + widths[col] += 1; + current += 1; + } + + widths +} + +fn increase_width(s: &str, width: usize, fill_with: char) -> String { + use crate::grid::util::string::string_width; + use std::{borrow::Cow, iter::repeat}; + + get_lines(s) + .map(|line| { + let length = string_width(&line); + + if length < width { + let mut line = line.into_owned(); + let remain = width - length; + line.extend(repeat(fill_with).take(remain)); + Cow::Owned(line) + } else { + line + } + }) + .collect::<Vec<_>>() + .join("\n") +} diff --git a/vendor/tabled/src/settings/width/mod.rs b/vendor/tabled/src/settings/width/mod.rs new file mode 100644 index 000000000..c1202f70f --- /dev/null +++ b/vendor/tabled/src/settings/width/mod.rs @@ -0,0 +1,163 @@ +//! This module contains object which can be used to limit a cell to a given width: +//! +//! - [`Truncate`] cuts a cell content to limit width. +//! - [`Wrap`] split the content via new lines in order to fit max width. +//! - [`Justify`] sets columns width to the same value. +//! +//! To set a a table width, a combination of [`Width::truncate`] or [`Width::wrap`] and [`Width::increase`] can be used. +//! +//! ## Example +//! +//! ``` +//! use tabled::{Table, settings::Width}; +//! +//! let table = Table::new(&["Hello World!"]) +//! .with(Width::wrap(7)) +//! .with(Width::increase(7)) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! "+-----+\n", +//! "| &st |\n", +//! "| r |\n", +//! "+-----+\n", +//! "| Hel |\n", +//! "| lo |\n", +//! "| Wor |\n", +//! "| ld! |\n", +//! "+-----+", +//! ) +//! ); +//! ``` + +mod justify; +mod min_width; +mod truncate; +mod util; +mod width_list; +mod wrap; + +use crate::settings::measurement::Measurement; + +pub use self::{ + justify::Justify, + min_width::MinWidth, + truncate::{SuffixLimit, Truncate}, + width_list::WidthList, + wrap::Wrap, +}; + +/// Width allows you to set a min and max width of an object on a [`Table`] +/// using different strategies. +/// +/// It also allows you to set a min and max width for a whole table. +/// +/// You can apply a min and max strategy at the same time with the same value, +/// the value will be a total table width. +/// +/// It is an abstract factory. +/// +/// Beware that borders are not removed when you set a size value to very small. +/// For example if you set size to 0 the table still be rendered but with all content removed. +/// +/// Also be aware that it doesn't changes [`Padding`] settings nor it considers them. +/// +/// The function is color aware if a `color` feature is on. +/// +/// ## Examples +/// +/// ### Cell change +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}}; +/// +/// let data = ["Hello", "World", "!"]; +/// +/// let table = Table::new(&data) +/// .with(Style::markdown()) +/// .with(Modify::new(Segment::all()).with(Width::truncate(3).suffix("..."))); +/// ``` +/// +/// ### Table change +/// +/// ``` +/// use tabled::{Table, settings::Width}; +/// +/// let table = Table::new(&["Hello World!"]).with(Width::wrap(5)); +/// ``` +/// +/// ### Total width +/// +/// ``` +/// use tabled::{Table, settings::Width}; +/// +/// let table = Table::new(&["Hello World!"]) +/// .with(Width::wrap(5)) +/// .with(Width::increase(5)); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct Width; + +impl Width { + /// Returns a [`Wrap`] structure. + pub fn wrap<W: Measurement<Width>>(width: W) -> Wrap<W> { + Wrap::new(width) + } + + /// Returns a [`Truncate`] structure. + pub fn truncate<W: Measurement<Width>>(width: W) -> Truncate<'static, W> { + Truncate::new(width) + } + + /// Returns a [`MinWidth`] structure. + pub fn increase<W: Measurement<Width>>(width: W) -> MinWidth<W> { + MinWidth::new(width) + } + + /// Returns a [`Justify`] structure. + pub fn justify<W: Measurement<Width>>(width: W) -> Justify<W> { + Justify::new(width) + } + + /// Create [`WidthList`] to set a table width to a constant list of column widths. + /// + /// Notice if you provide a list with `.len()` smaller than `Table::count_columns` then it will have no affect. + /// + /// Also notice that you must provide values bigger than or equal to a real content width, otherwise it may panic. + /// + /// # Example + /// + /// ``` + /// use tabled::{Table, settings::Width}; + /// + /// let data = vec![ + /// ("Some\ndata", "here", "and here"), + /// ("Some\ndata on a next", "line", "right here"), + /// ]; + /// + /// let table = Table::new(data) + /// .with(Width::list([20, 10, 12])) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+--------------------+----------+------------+\n\ + /// | &str | &str | &str |\n\ + /// +--------------------+----------+------------+\n\ + /// | Some | here | and here |\n\ + /// | data | | |\n\ + /// +--------------------+----------+------------+\n\ + /// | Some | line | right here |\n\ + /// | data on a next | | |\n\ + /// +--------------------+----------+------------+" + /// ) + /// ``` + pub fn list<I: IntoIterator<Item = usize>>(rows: I) -> WidthList { + WidthList::new(rows.into_iter().collect()) + } +} diff --git a/vendor/tabled/src/settings/width/truncate.rs b/vendor/tabled/src/settings/width/truncate.rs new file mode 100644 index 000000000..c7336f037 --- /dev/null +++ b/vendor/tabled/src/settings/width/truncate.rs @@ -0,0 +1,505 @@ +//! This module contains [`Truncate`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by truncating the width. +//! +//! [`Table`]: crate::Table + +use std::{borrow::Cow, iter, marker::PhantomData}; + +use crate::{ + grid::{ + config::{ColoredConfig, SpannedConfig}, + dimension::CompleteDimensionVecRecords, + records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut}, + util::string::{string_width, string_width_multiline}, + }, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + CellOption, TableOption, Width, + }, +}; + +use super::util::{cut_str, get_table_widths, get_table_widths_with_total}; + +/// Truncate cut the string to a given width if its length exceeds it. +/// Otherwise keeps the content of a cell untouched. +/// +/// The function is color aware if a `color` feature is on. +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Example +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, Width, Modify}}; +/// +/// let table = Table::new(&["Hello World!"]) +/// .with(Modify::new(Segment::all()).with(Width::truncate(3))); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Truncate<'a, W = usize, P = PriorityNone> { + width: W, + suffix: Option<TruncateSuffix<'a>>, + multiline: bool, + _priority: PhantomData<P>, +} +#[cfg(feature = "color")] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct TruncateSuffix<'a> { + text: Cow<'a, str>, + limit: SuffixLimit, + try_color: bool, +} + +#[cfg(not(feature = "color"))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct TruncateSuffix<'a> { + text: Cow<'a, str>, + limit: SuffixLimit, +} + +impl Default for TruncateSuffix<'_> { + fn default() -> Self { + Self { + text: Cow::default(), + limit: SuffixLimit::Cut, + #[cfg(feature = "color")] + try_color: false, + } + } +} + +/// A suffix limit settings. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SuffixLimit { + /// Cut the suffix. + Cut, + /// Don't show the suffix. + Ignore, + /// Use a string with n chars instead. + Replace(char), +} + +impl<W> Truncate<'static, W> +where + W: Measurement<Width>, +{ + /// Creates a [`Truncate`] object + pub fn new(width: W) -> Truncate<'static, W> { + Self { + width, + multiline: false, + suffix: None, + _priority: PhantomData, + } + } +} + +impl<'a, W, P> Truncate<'a, W, P> { + /// Sets a suffix which will be appended to a resultant string. + /// + /// The suffix is used in 3 circumstances: + /// 1. If original string is *bigger* than the suffix. + /// We cut more of the original string and append the suffix. + /// 2. If suffix is bigger than the original string. + /// We cut the suffix to fit in the width by default. + /// But you can peak the behaviour by using [`Truncate::suffix_limit`] + pub fn suffix<S: Into<Cow<'a, str>>>(self, suffix: S) -> Truncate<'a, W, P> { + let mut suff = self.suffix.unwrap_or_default(); + suff.text = suffix.into(); + + Truncate { + width: self.width, + multiline: self.multiline, + suffix: Some(suff), + _priority: PhantomData, + } + } + + /// Sets a suffix limit, which is used when the suffix is too big to be used. + pub fn suffix_limit(self, limit: SuffixLimit) -> Truncate<'a, W, P> { + let mut suff = self.suffix.unwrap_or_default(); + suff.limit = limit; + + Truncate { + width: self.width, + multiline: self.multiline, + suffix: Some(suff), + _priority: PhantomData, + } + } + + /// Use trancate logic per line, not as a string as a whole. + pub fn multiline(self) -> Truncate<'a, W, P> { + Truncate { + width: self.width, + multiline: true, + suffix: self.suffix, + _priority: self._priority, + } + } + + #[cfg(feature = "color")] + /// Sets a optional logic to try to colorize a suffix. + pub fn suffix_try_color(self, color: bool) -> Truncate<'a, W, P> { + let mut suff = self.suffix.unwrap_or_default(); + suff.try_color = color; + + Truncate { + width: self.width, + multiline: self.multiline, + suffix: Some(suff), + _priority: PhantomData, + } + } +} + +impl<'a, W, P> Truncate<'a, W, P> { + /// Priority defines the logic by which a truncate will be applied when is done for the whole table. + /// + /// - [`PriorityNone`] which cuts the columns one after another. + /// - [`PriorityMax`] cuts the biggest columns first. + /// - [`PriorityMin`] cuts the lowest columns first. + /// + /// [`PriorityMax`]: crate::settings::peaker::PriorityMax + /// [`PriorityMin`]: crate::settings::peaker::PriorityMin + pub fn priority<PP: Peaker>(self) -> Truncate<'a, W, PP> { + Truncate { + width: self.width, + multiline: self.multiline, + suffix: self.suffix, + _priority: PhantomData, + } + } +} + +impl Truncate<'_, (), ()> { + /// Truncate a given string + pub fn truncate_text(text: &str, width: usize) -> Cow<'_, str> { + truncate_text(text, width, "", false) + } +} + +impl<W, P, R> CellOption<R, ColoredConfig> for Truncate<'_, W, P> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: papergrid::config::Entity) { + let available = self.width.measure(&*records, cfg); + + let mut width = available; + let mut suffix = Cow::Borrowed(""); + + if let Some(x) = self.suffix.as_ref() { + let (cutted_suffix, rest_width) = make_suffix(x, width); + suffix = cutted_suffix; + width = rest_width; + }; + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let colorize = need_suffix_color_preservation(&self.suffix); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + + let cell_width = string_width_multiline(text); + if available >= cell_width { + continue; + } + + let text = + truncate_multiline(text, &suffix, width, available, colorize, self.multiline); + + records.set(pos, text.into_owned()); + } + } +} + +fn truncate_multiline<'a>( + text: &'a str, + suffix: &'a str, + width: usize, + twidth: usize, + suffix_color: bool, + multiline: bool, +) -> Cow<'a, str> { + if multiline { + let mut buf = String::new(); + for (i, line) in crate::grid::util::string::get_lines(text).enumerate() { + if i != 0 { + buf.push('\n'); + } + + let line = make_text_truncated(&line, suffix, width, twidth, suffix_color); + buf.push_str(&line); + } + + Cow::Owned(buf) + } else { + make_text_truncated(text, suffix, width, twidth, suffix_color) + } +} + +fn make_text_truncated<'a>( + text: &'a str, + suffix: &'a str, + width: usize, + twidth: usize, + suffix_color: bool, +) -> Cow<'a, str> { + if width == 0 { + if twidth == 0 { + Cow::Borrowed("") + } else { + Cow::Borrowed(suffix) + } + } else { + truncate_text(text, width, suffix, suffix_color) + } +} + +fn need_suffix_color_preservation(_suffix: &Option<TruncateSuffix<'_>>) -> bool { + #[cfg(not(feature = "color"))] + { + false + } + #[cfg(feature = "color")] + { + _suffix.as_ref().map_or(false, |s| s.try_color) + } +} + +fn make_suffix<'a>(suffix: &'a TruncateSuffix<'_>, width: usize) -> (Cow<'a, str>, usize) { + let suffix_length = string_width(&suffix.text); + if width > suffix_length { + return (Cow::Borrowed(suffix.text.as_ref()), width - suffix_length); + } + + match suffix.limit { + SuffixLimit::Ignore => (Cow::Borrowed(""), width), + SuffixLimit::Cut => { + let suffix = cut_str(&suffix.text, width); + (suffix, 0) + } + SuffixLimit::Replace(c) => { + let suffix = Cow::Owned(iter::repeat(c).take(width).collect()); + (suffix, 0) + } + } +} + +impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> + for Truncate<'_, W, P> +where + W: Measurement<Width>, + P: Peaker, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let width = self.width.measure(&*records, cfg); + let (widths, total) = get_table_widths_with_total(&*records, cfg); + if total <= width { + return; + } + + let suffix = self.suffix.as_ref().map(|s| TruncateSuffix { + text: Cow::Borrowed(&s.text), + limit: s.limit, + #[cfg(feature = "color")] + try_color: s.try_color, + }); + + let priority = P::create(); + let multiline = self.multiline; + let widths = truncate_total_width( + records, cfg, widths, total, width, priority, suffix, multiline, + ); + + let _ = dims.set_widths(widths); + } +} + +#[allow(clippy::too_many_arguments)] +fn truncate_total_width<P, R>( + records: &mut R, + cfg: &mut ColoredConfig, + mut widths: Vec<usize>, + total: usize, + width: usize, + priority: P, + suffix: Option<TruncateSuffix<'_>>, + multiline: bool, +) -> Vec<usize> +where + for<'a> &'a R: Records, + P: Peaker, + R: Records + PeekableRecords + ExactRecords + RecordsMut<String>, +{ + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + let min_widths = get_table_widths(EmptyRecords::new(count_rows, count_columns), cfg); + + decrease_widths(&mut widths, &min_widths, total, width, priority); + + let points = get_decrease_cell_list(cfg, &widths, &min_widths, (count_rows, count_columns)); + + for ((row, col), width) in points { + let mut truncate = Truncate::new(width); + truncate.suffix = suffix.clone(); + truncate.multiline = multiline; + CellOption::change(truncate, records, cfg, (row, col).into()); + } + + widths +} + +fn truncate_text<'a>( + text: &'a str, + width: usize, + suffix: &str, + _suffix_color: bool, +) -> Cow<'a, str> { + let content = cut_str(text, width); + if suffix.is_empty() { + return content; + } + + #[cfg(feature = "color")] + { + if _suffix_color { + if let Some(block) = ansi_str::get_blocks(text).last() { + if block.has_ansi() { + let style = block.style(); + Cow::Owned(format!( + "{}{}{}{}", + content, + style.start(), + suffix, + style.end() + )) + } else { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } + } else { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } + } else { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } + } + + #[cfg(not(feature = "color"))] + { + let mut content = content.into_owned(); + content.push_str(suffix); + Cow::Owned(content) + } +} + +fn get_decrease_cell_list( + cfg: &SpannedConfig, + widths: &[usize], + min_widths: &[usize], + shape: (usize, usize), +) -> Vec<((usize, usize), usize)> { + let mut points = Vec::new(); + (0..shape.1).for_each(|col| { + (0..shape.0) + .filter(|&row| cfg.is_cell_visible((row, col))) + .for_each(|row| { + let (width, width_min) = match cfg.get_column_span((row, col)) { + Some(span) => { + let width = (col..col + span).map(|i| widths[i]).sum::<usize>(); + let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>(); + let count_borders = count_borders(cfg, col, col + span, shape.1); + (width + count_borders, min_width + count_borders) + } + None => (widths[col], min_widths[col]), + }; + + if width >= width_min { + let padding = cfg.get_padding((row, col).into()); + let width = width.saturating_sub(padding.left.size + padding.right.size); + + points.push(((row, col), width)); + } + }); + }); + + points +} + +fn decrease_widths<F>( + widths: &mut [usize], + min_widths: &[usize], + total_width: usize, + mut width: usize, + mut peeaker: F, +) where + F: Peaker, +{ + let mut empty_list = 0; + for col in 0..widths.len() { + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + } + + while width != total_width { + if empty_list == widths.len() { + break; + } + + let col = match peeaker.peak(min_widths, widths) { + Some(col) => col, + None => break, + }; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + continue; + } + + widths[col] -= 1; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + + width += 1; + } +} + +fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, count_columns)) + .count() +} diff --git a/vendor/tabled/src/settings/width/util.rs b/vendor/tabled/src/settings/width/util.rs new file mode 100644 index 000000000..92cc48c41 --- /dev/null +++ b/vendor/tabled/src/settings/width/util.rs @@ -0,0 +1,265 @@ +use std::borrow::Cow; + +use crate::{ + grid::config::SpannedConfig, grid::dimension::SpannedGridDimension, grid::records::Records, +}; + +pub(crate) fn get_table_widths<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> { + SpannedGridDimension::width(records, cfg) +} + +pub(crate) fn get_table_widths_with_total<R: Records>( + records: R, + cfg: &SpannedConfig, +) -> (Vec<usize>, usize) { + let widths = SpannedGridDimension::width(records, cfg); + let total_width = get_table_total_width(&widths, cfg); + (widths, total_width) +} + +fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize { + let margin = cfg.get_margin(); + list.iter().sum::<usize>() + + cfg.count_vertical(list.len()) + + margin.left.size + + margin.right.size +} + +/// The function cuts the string to a specific width. +/// +/// BE AWARE: width is expected to be in bytes. +pub(crate) fn cut_str(s: &str, width: usize) -> Cow<'_, str> { + #[cfg(feature = "color")] + { + const REPLACEMENT: char = '\u{FFFD}'; + + let stripped = ansi_str::AnsiStr::ansi_strip(s); + let (length, count_unknowns, _) = split_at_pos(&stripped, width); + + let mut buf = ansi_str::AnsiStr::ansi_cut(s, ..length); + if count_unknowns > 0 { + let mut b = buf.into_owned(); + b.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); + buf = Cow::Owned(b); + } + + buf + } + + #[cfg(not(feature = "color"))] + { + cut_str_basic(s, width) + } +} + +/// The function cuts the string to a specific width. +/// +/// BE AWARE: width is expected to be in bytes. +#[cfg(not(feature = "color"))] +pub(crate) fn cut_str_basic(s: &str, width: usize) -> Cow<'_, str> { + const REPLACEMENT: char = '\u{FFFD}'; + + let (length, count_unknowns, _) = split_at_pos(s, width); + let buf = &s[..length]; + if count_unknowns == 0 { + return Cow::Borrowed(buf); + } + + let mut buf = buf.to_owned(); + buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); + + Cow::Owned(buf) +} + +/// The function splits a string in the position and +/// returns a exact number of bytes before the position and in case of a split in an unicode grapheme +/// a width of a character which was tried to be splited in. +/// +/// BE AWARE: pos is expected to be in bytes. +pub(crate) fn split_at_pos(s: &str, pos: usize) -> (usize, usize, usize) { + let mut length = 0; + let mut i = 0; + for c in s.chars() { + if i == pos { + break; + }; + + let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default(); + + // We cut the chars which takes more then 1 symbol to display, + // in order to archive the necessary width. + if i + c_width > pos { + let count = pos - i; + return (length, count, c.len_utf8()); + } + + i += c_width; + length += c.len_utf8(); + } + + (length, 0, 0) +} + +/// Strip OSC codes from `s`. If `s` is a single OSC8 hyperlink, with no other text, then return +/// (s_with_all_hyperlinks_removed, Some(url)). If `s` does not meet this description, then return +/// (s_with_all_hyperlinks_removed, None). Any ANSI color sequences in `s` will be retained. See +/// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda> +/// +/// The function is based on Dan Davison <https://github.com/dandavison> delta <https://github.com/dandavison/delta> ansi library. +#[cfg(feature = "color")] +pub(crate) fn strip_osc(text: &str) -> (String, Option<String>) { + #[derive(Debug)] + enum ExtractOsc8HyperlinkState { + ExpectOsc8Url, + ExpectFirstText, + ExpectMoreTextOrTerminator, + SeenOneHyperlink, + WillNotReturnUrl, + } + + use ExtractOsc8HyperlinkState::*; + + let mut url = None; + let mut state = ExpectOsc8Url; + let mut buf = String::with_capacity(text.len()); + + for el in ansitok::parse_ansi(text) { + match el.kind() { + ansitok::ElementKind::Osc => match state { + ExpectOsc8Url => { + url = Some(&text[el.start()..el.end()]); + state = ExpectFirstText; + } + ExpectMoreTextOrTerminator => state = SeenOneHyperlink, + _ => state = WillNotReturnUrl, + }, + ansitok::ElementKind::Sgr => buf.push_str(&text[el.start()..el.end()]), + ansitok::ElementKind::Csi => buf.push_str(&text[el.start()..el.end()]), + ansitok::ElementKind::Esc => {} + ansitok::ElementKind::Text => { + buf.push_str(&text[el.start()..el.end()]); + match state { + ExpectFirstText => state = ExpectMoreTextOrTerminator, + ExpectMoreTextOrTerminator => {} + _ => state = WillNotReturnUrl, + } + } + } + } + + match state { + WillNotReturnUrl => (buf, None), + _ => { + let url = url.and_then(|s| { + s.strip_prefix("\x1b]8;;") + .and_then(|s| s.strip_suffix('\x1b')) + }); + if let Some(url) = url { + (buf, Some(url.to_string())) + } else { + (buf, None) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::grid::util::string::string_width; + + #[cfg(feature = "color")] + use owo_colors::{colors::Yellow, OwoColorize}; + + #[test] + fn strip_test() { + assert_eq!(cut_str("123456", 0), ""); + assert_eq!(cut_str("123456", 3), "123"); + assert_eq!(cut_str("123456", 10), "123456"); + + assert_eq!(cut_str("a week ago", 4), "a we"); + + assert_eq!(cut_str("😳😳😳😳😳", 0), ""); + assert_eq!(cut_str("😳😳😳😳😳", 3), "😳�"); + assert_eq!(cut_str("😳😳😳😳😳", 4), "😳😳"); + assert_eq!(cut_str("😳😳😳😳😳", 20), "😳😳😳😳😳"); + + assert_eq!(cut_str("🏳️🏳️", 0), ""); + assert_eq!(cut_str("🏳️🏳️", 1), "🏳"); + assert_eq!(cut_str("🏳️🏳️", 2), "🏳\u{fe0f}🏳"); + assert_eq!(string_width("🏳️🏳️"), string_width("🏳\u{fe0f}🏳")); + + assert_eq!(cut_str("🎓", 1), "�"); + assert_eq!(cut_str("🎓", 2), "🎓"); + + assert_eq!(cut_str("🥿", 1), "�"); + assert_eq!(cut_str("🥿", 2), "🥿"); + + assert_eq!(cut_str("🩰", 1), "�"); + assert_eq!(cut_str("🩰", 2), "🩰"); + + assert_eq!(cut_str("👍🏿", 1), "�"); + assert_eq!(cut_str("👍🏿", 2), "👍"); + assert_eq!(cut_str("👍🏿", 3), "👍�"); + assert_eq!(cut_str("👍🏿", 4), "👍🏿"); + + assert_eq!(cut_str("🇻🇬", 1), "🇻"); + assert_eq!(cut_str("🇻🇬", 2), "🇻🇬"); + assert_eq!(cut_str("🇻🇬", 3), "🇻🇬"); + assert_eq!(cut_str("🇻🇬", 4), "🇻🇬"); + } + + #[cfg(feature = "color")] + #[test] + fn strip_color_test() { + let numbers = "123456".red().on_bright_black().to_string(); + + assert_eq!(cut_str(&numbers, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); + assert_eq!( + cut_str(&numbers, 3), + "\u{1b}[31;100m123\u{1b}[39m\u{1b}[49m" + ); + assert_eq!(cut_str(&numbers, 10), "\u{1b}[31;100m123456\u{1b}[0m"); + + let emojies = "😳😳😳😳😳".red().on_bright_black().to_string(); + + assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); + assert_eq!( + cut_str(&emojies, 3), + "\u{1b}[31;100m😳\u{1b}[39m\u{1b}[49m�" + ); + assert_eq!( + cut_str(&emojies, 4), + "\u{1b}[31;100m😳😳\u{1b}[39m\u{1b}[49m" + ); + assert_eq!(cut_str(&emojies, 20), "\u{1b}[31;100m😳😳😳😳😳\u{1b}[0m"); + + let emojies = "🏳️🏳️".red().on_bright_black().to_string(); + + assert_eq!(cut_str(&emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); + assert_eq!(cut_str(&emojies, 1), "\u{1b}[31;100m🏳\u{1b}[39m\u{1b}[49m"); + assert_eq!( + cut_str(&emojies, 2), + "\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m" + ); + assert_eq!( + string_width(&emojies), + string_width("\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m") + ); + } + + #[test] + #[cfg(feature = "color")] + fn test_color_strip() { + let s = "Collored string" + .fg::<Yellow>() + .on_truecolor(12, 200, 100) + .blink() + .to_string(); + assert_eq!( + cut_str(&s, 1), + "\u{1b}[5m\u{1b}[48;2;12;200;100m\u{1b}[33mC\u{1b}[25m\u{1b}[39m\u{1b}[49m" + ) + } +} diff --git a/vendor/tabled/src/settings/width/width_list.rs b/vendor/tabled/src/settings/width/width_list.rs new file mode 100644 index 000000000..7547b97f3 --- /dev/null +++ b/vendor/tabled/src/settings/width/width_list.rs @@ -0,0 +1,50 @@ +use std::iter::FromIterator; + +use crate::{ + grid::dimension::CompleteDimensionVecRecords, grid::records::Records, settings::TableOption, +}; + +/// A structure used to set [`Table`] width via a list of columns widths. +/// +/// [`Table`]: crate::Table +#[derive(Debug)] +pub struct WidthList { + list: Vec<usize>, +} + +impl WidthList { + /// Creates a new object. + pub fn new(list: Vec<usize>) -> Self { + Self { list } + } +} + +impl From<Vec<usize>> for WidthList { + fn from(list: Vec<usize>) -> Self { + Self::new(list) + } +} + +impl FromIterator<usize> for WidthList { + fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self { + Self::new(iter.into_iter().collect()) + } +} + +impl<R, C> TableOption<R, CompleteDimensionVecRecords<'static>, C> for WidthList +where + R: Records, +{ + fn change( + self, + records: &mut R, + _: &mut C, + dimension: &mut CompleteDimensionVecRecords<'static>, + ) { + if self.list.len() < records.count_columns() { + return; + } + + let _ = dimension.set_widths(self.list); + } +} diff --git a/vendor/tabled/src/settings/width/wrap.rs b/vendor/tabled/src/settings/width/wrap.rs new file mode 100644 index 000000000..96a370408 --- /dev/null +++ b/vendor/tabled/src/settings/width/wrap.rs @@ -0,0 +1,1468 @@ +//! This module contains [`Wrap`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by wrapping it's content +//! to a new line. +//! +//! [`Table`]: crate::Table + +use std::marker::PhantomData; + +use crate::{ + grid::config::ColoredConfig, + grid::dimension::CompleteDimensionVecRecords, + grid::records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut}, + grid::{config::Entity, config::SpannedConfig, util::string::string_width_multiline}, + settings::{ + measurement::Measurement, + peaker::{Peaker, PriorityNone}, + width::Width, + CellOption, TableOption, + }, +}; + +use super::util::{get_table_widths, get_table_widths_with_total, split_at_pos}; + +/// Wrap wraps a string to a new line in case it exceeds the provided max boundary. +/// Otherwise keeps the content of a cell untouched. +/// +/// The function is color aware if a `color` feature is on. +/// +/// Be aware that it doesn't consider padding. +/// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. +/// +/// ## Example +/// +/// ``` +/// use tabled::{Table, settings::{object::Segment, width::Width, Modify}}; +/// +/// let table = Table::new(&["Hello World!"]) +/// .with(Modify::new(Segment::all()).with(Width::wrap(3))); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +#[derive(Debug, Clone)] +pub struct Wrap<W = usize, P = PriorityNone> { + width: W, + keep_words: bool, + _priority: PhantomData<P>, +} + +impl<W> Wrap<W> { + /// Creates a [`Wrap`] object + pub fn new(width: W) -> Self + where + W: Measurement<Width>, + { + Wrap { + width, + keep_words: false, + _priority: PhantomData, + } + } +} + +impl<W, P> Wrap<W, P> { + /// Priority defines the logic by which a truncate will be applied when is done for the whole table. + /// + /// - [`PriorityNone`] which cuts the columns one after another. + /// - [`PriorityMax`] cuts the biggest columns first. + /// - [`PriorityMin`] cuts the lowest columns first. + /// + /// Be aware that it doesn't consider padding. + /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. + /// + /// [`Padding`]: crate::settings::Padding + /// [`PriorityMax`]: crate::settings::peaker::PriorityMax + /// [`PriorityMin`]: crate::settings::peaker::PriorityMin + pub fn priority<PP>(self) -> Wrap<W, PP> { + Wrap { + width: self.width, + keep_words: self.keep_words, + _priority: PhantomData, + } + } + + /// Set the keep words option. + /// + /// If a wrapping point will be in a word, [`Wrap`] will + /// preserve a word (if possible) and wrap the string before it. + pub fn keep_words(mut self) -> Self { + self.keep_words = true; + self + } +} + +impl Wrap<(), ()> { + /// Wrap a given string + pub fn wrap_text(text: &str, width: usize, keeping_words: bool) -> String { + wrap_text(text, width, keeping_words) + } +} + +impl<W, P, R> TableOption<R, CompleteDimensionVecRecords<'static>, ColoredConfig> for Wrap<W, P> +where + W: Measurement<Width>, + P: Peaker, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change( + self, + records: &mut R, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords<'static>, + ) { + if records.count_rows() == 0 || records.count_columns() == 0 { + return; + } + + let width = self.width.measure(&*records, cfg); + let (widths, total) = get_table_widths_with_total(&*records, cfg); + if width >= total { + return; + } + + let priority = P::create(); + let keep_words = self.keep_words; + let widths = wrap_total_width(records, cfg, widths, total, width, keep_words, priority); + + let _ = dims.set_widths(widths); + } +} + +impl<W, R> CellOption<R, ColoredConfig> for Wrap<W> +where + W: Measurement<Width>, + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + for<'a> &'a R: Records, +{ + fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { + let width = self.width.measure(&*records, cfg); + + let count_rows = records.count_rows(); + let count_columns = records.count_columns(); + + for pos in entity.iter(count_rows, count_columns) { + let is_valid_pos = pos.0 < records.count_rows() && pos.1 < records.count_columns(); + if !is_valid_pos { + continue; + } + + let text = records.get_text(pos); + let cell_width = string_width_multiline(text); + if cell_width <= width { + continue; + } + + let wrapped = wrap_text(text, width, self.keep_words); + records.set(pos, wrapped); + } + } +} + +fn wrap_total_width<R, P>( + records: &mut R, + cfg: &mut ColoredConfig, + mut widths: Vec<usize>, + total_width: usize, + width: usize, + keep_words: bool, + priority: P, +) -> Vec<usize> +where + R: Records + ExactRecords + PeekableRecords + RecordsMut<String>, + P: Peaker, + for<'a> &'a R: Records, +{ + let shape = (records.count_rows(), records.count_columns()); + let min_widths = get_table_widths(EmptyRecords::from(shape), cfg); + + decrease_widths(&mut widths, &min_widths, total_width, width, priority); + + let points = get_decrease_cell_list(cfg, &widths, &min_widths, shape); + + for ((row, col), width) in points { + let mut wrap = Wrap::new(width); + wrap.keep_words = keep_words; + <Wrap as CellOption<_, _>>::change(wrap, records, cfg, (row, col).into()); + } + + widths +} + +#[cfg(not(feature = "color"))] +pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { + if width == 0 { + return String::new(); + } + + if keep_words { + split_keeping_words(text, width, "\n") + } else { + chunks(text, width).join("\n") + } +} + +#[cfg(feature = "color")] +pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { + use super::util::strip_osc; + + if width == 0 { + return String::new(); + } + + let (text, url): (String, Option<String>) = strip_osc(text); + let (prefix, suffix) = build_link_prefix_suffix(url); + + if keep_words { + split_keeping_words(&text, width, &prefix, &suffix) + } else { + chunks(&text, width, &prefix, &suffix).join("\n") + } +} + +#[cfg(feature = "color")] +fn build_link_prefix_suffix(url: Option<String>) -> (String, String) { + match url { + Some(url) => { + // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + let osc8 = "\x1b]8;;"; + let st = "\x1b\\"; + + (format!("{osc8}{url}{st}"), format!("{osc8}{st}")) + } + None => ("".to_string(), "".to_string()), + } +} + +#[cfg(not(feature = "color"))] +fn chunks(s: &str, width: usize) -> Vec<String> { + if width == 0 { + return Vec::new(); + } + + const REPLACEMENT: char = '\u{FFFD}'; + + let mut buf = String::with_capacity(width); + let mut list = Vec::new(); + let mut i = 0; + for c in s.chars() { + let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default(); + if i + c_width > width { + let count_unknowns = width - i; + buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); + i += count_unknowns; + } else { + buf.push(c); + i += c_width; + } + + if i == width { + list.push(buf); + buf = String::with_capacity(width); + i = 0; + } + } + + if !buf.is_empty() { + list.push(buf); + } + + list +} + +#[cfg(feature = "color")] +fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec<String> { + use std::fmt::Write; + + if width == 0 { + return Vec::new(); + } + + let mut list = Vec::new(); + let mut line = String::with_capacity(width); + let mut line_width = 0; + + for b in ansi_str::get_blocks(s) { + let text_style = b.style(); + let mut text_slice = b.text(); + if text_slice.is_empty() { + continue; + } + + let available_space = width - line_width; + if available_space == 0 { + list.push(line); + line = String::with_capacity(width); + line_width = 0; + } + + line.push_str(prefix); + let _ = write!(&mut line, "{}", text_style.start()); + + while !text_slice.is_empty() { + let available_space = width - line_width; + + let part_width = unicode_width::UnicodeWidthStr::width(text_slice); + if part_width <= available_space { + line.push_str(text_slice); + line_width += part_width; + + if available_space == 0 { + let _ = write!(&mut line, "{}", text_style.end()); + line.push_str(suffix); + list.push(line); + line = String::with_capacity(width); + line.push_str(prefix); + line_width = 0; + let _ = write!(&mut line, "{}", text_style.start()); + } + + break; + } + + let (lhs, rhs, (unknowns, split_char)) = split_string_at(text_slice, available_space); + + text_slice = &rhs[split_char..]; + + line.push_str(lhs); + line_width += unicode_width::UnicodeWidthStr::width(lhs); + + const REPLACEMENT: char = '\u{FFFD}'; + line.extend(std::iter::repeat(REPLACEMENT).take(unknowns)); + line_width += unknowns; + + if line_width == width { + let _ = write!(&mut line, "{}", text_style.end()); + line.push_str(suffix); + list.push(line); + line = String::with_capacity(width); + line.push_str(prefix); + line_width = 0; + let _ = write!(&mut line, "{}", text_style.start()); + } + } + + if line_width > 0 { + let _ = write!(&mut line, "{}", text_style.end()); + } + } + + if line_width > 0 { + line.push_str(suffix); + list.push(line); + } + + list +} + +#[cfg(not(feature = "color"))] +fn split_keeping_words(s: &str, width: usize, sep: &str) -> String { + const REPLACEMENT: char = '\u{FFFD}'; + + let mut lines = Vec::new(); + let mut line = String::with_capacity(width); + let mut line_width = 0; + + let mut is_first_word = true; + + for word in s.split(' ') { + if !is_first_word { + let line_has_space = line_width < width; + if line_has_space { + line.push(' '); + line_width += 1; + is_first_word = false; + } + } + + if is_first_word { + is_first_word = false; + } + + let word_width = unicode_width::UnicodeWidthStr::width(word); + + let line_has_space = line_width + word_width <= width; + if line_has_space { + line.push_str(word); + line_width += word_width; + continue; + } + + if word_width <= width { + // the word can be fit to 'width' so we put it on new line + + line.extend(std::iter::repeat(' ').take(width - line_width)); + lines.push(line); + + line = String::with_capacity(width); + line_width = 0; + + line.push_str(word); + line_width += word_width; + is_first_word = false; + } else { + // the word is too long any way so we split it + + let mut word_part = word; + while !word_part.is_empty() { + let available_space = width - line_width; + let (lhs, rhs, (unknowns, split_char)) = + split_string_at(word_part, available_space); + + word_part = &rhs[split_char..]; + line_width += unicode_width::UnicodeWidthStr::width(lhs) + unknowns; + is_first_word = false; + + line.push_str(lhs); + line.extend(std::iter::repeat(REPLACEMENT).take(unknowns)); + + if line_width == width { + lines.push(line); + line = String::with_capacity(width); + line_width = 0; + is_first_word = true; + } + } + } + } + + if line_width > 0 { + line.extend(std::iter::repeat(' ').take(width - line_width)); + lines.push(line); + } + + lines.join(sep) +} + +#[cfg(feature = "color")] +fn split_keeping_words(text: &str, width: usize, prefix: &str, suffix: &str) -> String { + if text.is_empty() || width == 0 { + return String::new(); + } + + let stripped_text = ansi_str::AnsiStr::ansi_strip(text); + let mut word_width = 0; + let mut word_chars = 0; + let mut blocks = parsing::Blocks::new(ansi_str::get_blocks(text)); + let mut buf = parsing::MultilineBuffer::new(width); + buf.set_prefix(prefix); + buf.set_suffix(suffix); + + for c in stripped_text.chars() { + match c { + ' ' => { + parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1); + word_chars = 0; + word_width = 0; + } + '\n' => { + parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1); + word_chars = 0; + word_width = 0; + } + _ => { + word_width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + word_chars += 1; + } + } + } + + if word_chars > 0 { + parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 0); + buf.finish_line(&blocks); + } + + buf.into_string() +} + +#[cfg(feature = "color")] +mod parsing { + use ansi_str::{AnsiBlock, AnsiBlockIter, Style}; + use std::fmt::Write; + + pub(super) struct Blocks<'a> { + iter: AnsiBlockIter<'a>, + current: Option<RelativeBlock<'a>>, + } + + impl<'a> Blocks<'a> { + pub(super) fn new(iter: AnsiBlockIter<'a>) -> Self { + Self { + iter, + current: None, + } + } + + pub(super) fn next_block(&mut self) -> Option<RelativeBlock<'a>> { + self.current + .take() + .or_else(|| self.iter.next().map(RelativeBlock::new)) + } + } + + pub(super) struct RelativeBlock<'a> { + block: AnsiBlock<'a>, + pos: usize, + } + + impl<'a> RelativeBlock<'a> { + pub(super) fn new(block: AnsiBlock<'a>) -> Self { + Self { block, pos: 0 } + } + + pub(super) fn get_text(&self) -> &str { + &self.block.text()[self.pos..] + } + + pub(super) fn get_origin(&self) -> &str { + self.block.text() + } + + pub(super) fn get_style(&self) -> &Style { + self.block.style() + } + } + + pub(super) struct MultilineBuffer<'a> { + buf: String, + width_last: usize, + width: usize, + prefix: &'a str, + suffix: &'a str, + } + + impl<'a> MultilineBuffer<'a> { + pub(super) fn new(width: usize) -> Self { + Self { + buf: String::new(), + width_last: 0, + prefix: "", + suffix: "", + width, + } + } + + pub(super) fn into_string(self) -> String { + self.buf + } + + pub(super) fn set_suffix(&mut self, suffix: &'a str) { + self.suffix = suffix; + } + + pub(super) fn set_prefix(&mut self, prefix: &'a str) { + self.prefix = prefix; + } + + pub(super) fn max_width(&self) -> usize { + self.width + } + + pub(super) fn available_width(&self) -> usize { + self.width - self.width_last + } + + pub(super) fn fill(&mut self, c: char) -> usize { + debug_assert_eq!(unicode_width::UnicodeWidthChar::width(c), Some(1)); + + let rest_width = self.available_width(); + for _ in 0..rest_width { + self.buf.push(c); + } + + rest_width + } + + pub(super) fn set_next_line(&mut self, blocks: &Blocks<'_>) { + if let Some(block) = &blocks.current { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } + + self.buf.push_str(self.suffix); + + let _ = self.fill(' '); + self.buf.push('\n'); + self.width_last = 0; + + self.buf.push_str(self.prefix); + + if let Some(block) = &blocks.current { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + } + } + + pub(super) fn finish_line(&mut self, blocks: &Blocks<'_>) { + if let Some(block) = &blocks.current { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } + + self.buf.push_str(self.suffix); + + let _ = self.fill(' '); + self.width_last = 0; + } + + pub(super) fn read_chars(&mut self, block: &RelativeBlock<'_>, n: usize) -> (usize, usize) { + let mut count_chars = 0; + let mut count_bytes = 0; + for c in block.get_text().chars() { + if count_chars == n { + break; + } + + count_chars += 1; + count_bytes += c.len_utf8(); + + let cwidth = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + + let available_space = self.width - self.width_last; + if available_space == 0 { + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + self.buf.push_str(self.suffix); + self.buf.push('\n'); + self.buf.push_str(self.prefix); + let _ = self + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + self.width_last = 0; + } + + let is_enough_space = self.width_last + cwidth <= self.width; + if !is_enough_space { + // thereatically a cwidth can be 2 but buf_width is 1 + // but it handled here too; + + const REPLACEMENT: char = '\u{FFFD}'; + let _ = self.fill(REPLACEMENT); + self.width_last = self.width; + } else { + self.buf.push(c); + self.width_last += cwidth; + } + } + + (count_chars, count_bytes) + } + + pub(super) fn read_chars_unchecked( + &mut self, + block: &RelativeBlock<'_>, + n: usize, + ) -> (usize, usize) { + let mut count_chars = 0; + let mut count_bytes = 0; + for c in block.get_text().chars() { + if count_chars == n { + break; + } + + count_chars += 1; + count_bytes += c.len_utf8(); + + let cwidth = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + self.width_last += cwidth; + + self.buf.push(c); + } + + debug_assert!(self.width_last <= self.width); + + (count_chars, count_bytes) + } + } + + pub(super) fn read_chars(buf: &mut MultilineBuffer<'_>, blocks: &mut Blocks<'_>, n: usize) { + let mut n = n; + while n > 0 { + let is_new_block = blocks.current.is_none(); + let mut block = blocks.next_block().expect("Must never happen"); + if is_new_block { + buf.buf.push_str(buf.prefix); + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + } + + let (read_count, read_bytes) = buf.read_chars(&block, n); + + if block.pos + read_bytes == block.get_origin().len() { + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } else { + block.pos += read_bytes; + blocks.current = Some(block); + } + + n -= read_count; + } + } + + pub(super) fn read_chars_unchecked( + buf: &mut MultilineBuffer<'_>, + blocks: &mut Blocks<'_>, + n: usize, + ) { + let mut n = n; + while n > 0 { + let is_new_block = blocks.current.is_none(); + let mut block = blocks.next_block().expect("Must never happen"); + + if is_new_block { + buf.buf.push_str(buf.prefix); + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().start())); + } + + let (read_count, read_bytes) = buf.read_chars_unchecked(&block, n); + + if block.pos + read_bytes == block.get_origin().len() { + let _ = buf + .buf + .write_fmt(format_args!("{}", block.get_style().end())); + } else { + block.pos += read_bytes; + blocks.current = Some(block); + } + + n -= read_count; + } + } + + pub(super) fn handle_word( + buf: &mut MultilineBuffer<'_>, + blocks: &mut Blocks<'_>, + word_chars: usize, + word_width: usize, + additional_read: usize, + ) { + if word_chars > 0 { + let has_line_space = word_width <= buf.available_width(); + let is_word_too_big = word_width > buf.max_width(); + + if is_word_too_big { + read_chars(buf, blocks, word_chars + additional_read); + } else if has_line_space { + read_chars_unchecked(buf, blocks, word_chars); + if additional_read > 0 { + read_chars(buf, blocks, additional_read); + } + } else { + buf.set_next_line(&*blocks); + read_chars_unchecked(buf, blocks, word_chars); + if additional_read > 0 { + read_chars(buf, blocks, additional_read); + } + } + + return; + } + + let has_current_line_space = additional_read <= buf.available_width(); + if has_current_line_space { + read_chars_unchecked(buf, blocks, additional_read); + } else { + buf.set_next_line(&*blocks); + read_chars_unchecked(buf, blocks, additional_read); + } + } +} + +fn split_string_at(text: &str, at: usize) -> (&str, &str, (usize, usize)) { + let (length, count_unknowns, split_char_size) = split_at_pos(text, at); + let (lhs, rhs) = text.split_at(length); + + (lhs, rhs, (count_unknowns, split_char_size)) +} + +fn decrease_widths<F>( + widths: &mut [usize], + min_widths: &[usize], + total_width: usize, + mut width: usize, + mut peeaker: F, +) where + F: Peaker, +{ + let mut empty_list = 0; + for col in 0..widths.len() { + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + } + + while width != total_width { + if empty_list == widths.len() { + break; + } + + let col = match peeaker.peak(min_widths, widths) { + Some(col) => col, + None => break, + }; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + continue; + } + + widths[col] -= 1; + + if widths[col] == 0 || widths[col] <= min_widths[col] { + empty_list += 1; + } + + width += 1; + } +} + +fn get_decrease_cell_list( + cfg: &SpannedConfig, + widths: &[usize], + min_widths: &[usize], + shape: (usize, usize), +) -> Vec<((usize, usize), usize)> { + let mut points = Vec::new(); + (0..shape.1).for_each(|col| { + (0..shape.0) + .filter(|&row| cfg.is_cell_visible((row, col))) + .for_each(|row| { + let (width, width_min) = match cfg.get_column_span((row, col)) { + Some(span) => { + let width = (col..col + span).map(|i| widths[i]).sum::<usize>(); + let min_width = (col..col + span).map(|i| min_widths[i]).sum::<usize>(); + let count_borders = count_borders(cfg, col, col + span, shape.1); + (width + count_borders, min_width + count_borders) + } + None => (widths[col], min_widths[col]), + }; + + if width >= width_min { + let padding = cfg.get_padding((row, col).into()); + let width = width.saturating_sub(padding.left.size + padding.right.size); + + points.push(((row, col), width)); + } + }); + }); + + points +} + +fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, count_columns)) + .count() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn split_test() { + #[cfg(not(feature = "color"))] + let split = |text, width| chunks(text, width).join("\n"); + + #[cfg(feature = "color")] + let split = |text, width| chunks(text, width, "", "").join("\n"); + + assert_eq!(split("123456", 0), ""); + + assert_eq!(split("123456", 1), "1\n2\n3\n4\n5\n6"); + assert_eq!(split("123456", 2), "12\n34\n56"); + assert_eq!(split("12345", 2), "12\n34\n5"); + assert_eq!(split("123456", 6), "123456"); + assert_eq!(split("123456", 10), "123456"); + + assert_eq!(split("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); + assert_eq!(split("😳😳😳😳😳", 2), "😳\n😳\n😳\n😳\n😳"); + assert_eq!(split("😳😳😳😳😳", 3), "😳�\n😳�\n😳"); + assert_eq!(split("😳😳😳😳😳", 6), "😳😳😳\n😳😳"); + assert_eq!(split("😳😳😳😳😳", 20), "😳😳😳😳😳"); + + assert_eq!(split("😳123😳", 1), "�\n1\n2\n3\n�"); + assert_eq!(split("😳12😳3", 1), "�\n1\n2\n�\n3"); + } + + #[test] + fn chunks_test() { + #[allow(clippy::redundant_closure)] + #[cfg(not(feature = "color"))] + let chunks = |text, width| chunks(text, width); + + #[cfg(feature = "color")] + let chunks = |text, width| chunks(text, width, "", ""); + + assert_eq!(chunks("123456", 0), [""; 0]); + + assert_eq!(chunks("123456", 1), ["1", "2", "3", "4", "5", "6"]); + assert_eq!(chunks("123456", 2), ["12", "34", "56"]); + assert_eq!(chunks("12345", 2), ["12", "34", "5"]); + + assert_eq!(chunks("😳😳😳😳😳", 1), ["�", "�", "�", "�", "�"]); + assert_eq!(chunks("😳😳😳😳😳", 2), ["😳", "😳", "😳", "😳", "😳"]); + assert_eq!(chunks("😳😳😳😳😳", 3), ["😳�", "😳�", "😳"]); + } + + #[cfg(not(feature = "color"))] + #[test] + fn split_by_line_keeping_words_test() { + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6"); + assert_eq!(split_keeping_words("123456", 2), "12\n34\n56"); + assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 "); + + assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); + + assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 "); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_test() { + #[cfg(feature = "color")] + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6"); + assert_eq!(split_keeping_words("123456", 2), "12\n34\n56"); + assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 "); + + assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); + + assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 "); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_test() { + #[cfg(feature = "color")] + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + #[cfg(not(feature = "color"))] + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + let text = "\u{1b}[36mJapanese “vacancy” button\u{1b}[0m"; + + assert_eq!(split_keeping_words(text, 2), "\u{1b}[36mJa\u{1b}[39m\n\u{1b}[36mpa\u{1b}[39m\n\u{1b}[36mne\u{1b}[39m\n\u{1b}[36mse\u{1b}[39m\n\u{1b}[36m “\u{1b}[39m\n\u{1b}[36mva\u{1b}[39m\n\u{1b}[36mca\u{1b}[39m\n\u{1b}[36mnc\u{1b}[39m\n\u{1b}[36my”\u{1b}[39m\n\u{1b}[36m b\u{1b}[39m\n\u{1b}[36mut\u{1b}[39m\n\u{1b}[36mto\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m "); + assert_eq!(split_keeping_words(text, 1), "\u{1b}[36mJ\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mp\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36ms\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36m“\u{1b}[39m\n\u{1b}[36mv\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36my\u{1b}[39m\n\u{1b}[36m”\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mb\u{1b}[39m\n\u{1b}[36mu\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mo\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m"); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_2_test() { + use ansi_str::AnsiStr; + + #[cfg(feature = "color")] + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + #[cfg(not(feature = "color"))] + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m"; + + assert_eq!( + split_keeping_words(text, 2) + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "\u{1b}[37mTi\u{1b}[39m", + "\u{1b}[37mgr\u{1b}[39m", + "\u{1b}[37me \u{1b}[39m", + "\u{1b}[37mEc\u{1b}[39m", + "\u{1b}[37mua\u{1b}[39m", + "\u{1b}[37mdo\u{1b}[39m", + "\u{1b}[37mr \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mOM\u{1b}[39m", + "\u{1b}[37mYA\u{1b}[39m", + "\u{1b}[37m A\u{1b}[39m", + "\u{1b}[37mnd\u{1b}[39m", + "\u{1b}[37min\u{1b}[39m", + "\u{1b}[37ma \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m38\u{1b}[39m", + "\u{1b}[37m24\u{1b}[39m", + "\u{1b}[37m90\u{1b}[39m", + "\u{1b}[37m99\u{1b}[39m", + "\u{1b}[37m99\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mCa\u{1b}[39m", + "\u{1b}[37mlc\u{1b}[39m", + "\u{1b}[37miu\u{1b}[39m", + "\u{1b}[37mm \u{1b}[39m", + "\u{1b}[37mca\u{1b}[39m", + "\u{1b}[37mrb\u{1b}[39m", + "\u{1b}[37mon\u{1b}[39m", + "\u{1b}[37mat\u{1b}[39m", + "\u{1b}[37me \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mCo\u{1b}[39m", + "\u{1b}[37mlo\u{1b}[39m", + "\u{1b}[37mmb\u{1b}[39m", + "\u{1b}[37mia\u{1b}[39m" + ] + ); + + assert_eq!( + split_keeping_words(text, 1) + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "\u{1b}[37mT\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37mg\u{1b}[39m", + "\u{1b}[37mr\u{1b}[39m", + "\u{1b}[37me\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mE\u{1b}[39m", + "\u{1b}[37mc\u{1b}[39m", + "\u{1b}[37mu\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37md\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37mr\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mO\u{1b}[39m", + "\u{1b}[37mM\u{1b}[39m", + "\u{1b}[37mY\u{1b}[39m", + "\u{1b}[37mA\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mA\u{1b}[39m", + "\u{1b}[37mn\u{1b}[39m", + "\u{1b}[37md\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37mn\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m3\u{1b}[39m", + "\u{1b}[37m8\u{1b}[39m", + "\u{1b}[37m2\u{1b}[39m", + "\u{1b}[37m4\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m0\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m9\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mC\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37ml\u{1b}[39m", + "\u{1b}[37mc\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37mu\u{1b}[39m", + "\u{1b}[37mm\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mc\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37mr\u{1b}[39m", + "\u{1b}[37mb\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37mn\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m", + "\u{1b}[37mt\u{1b}[39m", + "\u{1b}[37me\u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37m \u{1b}[39m", + "\u{1b}[37mC\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37ml\u{1b}[39m", + "\u{1b}[37mo\u{1b}[39m", + "\u{1b}[37mm\u{1b}[39m", + "\u{1b}[37mb\u{1b}[39m", + "\u{1b}[37mi\u{1b}[39m", + "\u{1b}[37ma\u{1b}[39m" + ] + ) + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_3_test() { + let split = |text, width| split_keeping_words(text, width, "", ""); + assert_eq!( + split( + "\u{1b}[37m🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻\u{1b}[0m", + 3, + ), + "\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m", + ); + assert_eq!( + split("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7), + "\u{1b}[37mthis is\u{1b}[39m\n\u{1b}[37m a long\u{1b}[39m\n\u{1b}[37m senten\u{1b}[39m\n\u{1b}[37mce\u{1b}[39m " + ); + assert_eq!( + split("\u{1b}[37mHello World\u{1b}[0m", 7), + "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWorld\u{1b}[39m " + ); + assert_eq!( + split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 7), + "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m " + ); + assert_eq!( + split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 8), + "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m " + ); + } + + #[cfg(not(feature = "color"))] + #[test] + fn split_keeping_words_4_test() { + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 "); + assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78"); + } + + #[cfg(feature = "color")] + #[test] + fn split_keeping_words_4_test() { + let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); + + #[cfg(not(feature = "color"))] + let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); + + assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 "); + assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78"); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_test_with_prefix_and_suffix() { + assert_eq!(chunks("123456", 0, "^", "$"), ["^$"; 0]); + + assert_eq!( + chunks("123456", 1, "^", "$"), + ["^1$", "^2$", "^3$", "^4$", "^5$", "^6$"] + ); + assert_eq!(chunks("123456", 2, "^", "$"), ["^12$", "^34$", "^56$"]); + assert_eq!(chunks("12345", 2, "^", "$"), ["^12$", "^34$", "^5$"]); + + assert_eq!( + chunks("😳😳😳😳😳", 1, "^", "$"), + ["^�$", "^�$", "^�$", "^�$", "^�$"] + ); + assert_eq!( + chunks("😳😳😳😳😳", 2, "^", "$"), + ["^😳$", "^😳$", "^😳$", "^😳$", "^😳$"] + ); + assert_eq!( + chunks("😳😳😳😳😳", 3, "^", "$"), + ["^😳�$", "^😳�$", "^😳$"] + ); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_test_with_prefix_and_suffix() { + assert_eq!( + split_keeping_words("123456", 1, "^", "$"), + "^1$\n^2$\n^3$\n^4$\n^5$\n^6$" + ); + assert_eq!( + split_keeping_words("123456", 2, "^", "$"), + "^12$\n^34$\n^56$" + ); + assert_eq!( + split_keeping_words("12345", 2, "^", "$"), + "^12$\n^34$\n^5$ " + ); + + assert_eq!( + split_keeping_words("😳😳😳😳😳", 1, "^", "$"), + "^�$\n^�$\n^�$\n^�$\n^�$" + ); + } + + #[cfg(feature = "color")] + #[test] + fn split_by_line_keeping_words_color_2_test_with_prefix_and_suffix() { + use ansi_str::AnsiStr; + + let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m"; + + assert_eq!( + split_keeping_words(text, 2, "^", "$") + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "^\u{1b}[37mTi\u{1b}[39m$", + "^\u{1b}[37mgr\u{1b}[39m$", + "^\u{1b}[37me \u{1b}[39m$", + "^\u{1b}[37mEc\u{1b}[39m$", + "^\u{1b}[37mua\u{1b}[39m$", + "^\u{1b}[37mdo\u{1b}[39m$", + "^\u{1b}[37mr \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mOM\u{1b}[39m$", + "^\u{1b}[37mYA\u{1b}[39m$", + "^\u{1b}[37m A\u{1b}[39m$", + "^\u{1b}[37mnd\u{1b}[39m$", + "^\u{1b}[37min\u{1b}[39m$", + "^\u{1b}[37ma \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m38\u{1b}[39m$", + "^\u{1b}[37m24\u{1b}[39m$", + "^\u{1b}[37m90\u{1b}[39m$", + "^\u{1b}[37m99\u{1b}[39m$", + "^\u{1b}[37m99\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mCa\u{1b}[39m$", + "^\u{1b}[37mlc\u{1b}[39m$", + "^\u{1b}[37miu\u{1b}[39m$", + "^\u{1b}[37mm \u{1b}[39m$", + "^\u{1b}[37mca\u{1b}[39m$", + "^\u{1b}[37mrb\u{1b}[39m$", + "^\u{1b}[37mon\u{1b}[39m$", + "^\u{1b}[37mat\u{1b}[39m$", + "^\u{1b}[37me \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mCo\u{1b}[39m$", + "^\u{1b}[37mlo\u{1b}[39m$", + "^\u{1b}[37mmb\u{1b}[39m$", + "^\u{1b}[37mia\u{1b}[39m$" + ] + ); + + assert_eq!( + split_keeping_words(text, 1, "^", "$") + .ansi_split("\n") + .collect::<Vec<_>>(), + [ + "^\u{1b}[37mT\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37mg\u{1b}[39m$", + "^\u{1b}[37mr\u{1b}[39m$", + "^\u{1b}[37me\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mE\u{1b}[39m$", + "^\u{1b}[37mc\u{1b}[39m$", + "^\u{1b}[37mu\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37md\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37mr\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mO\u{1b}[39m$", + "^\u{1b}[37mM\u{1b}[39m$", + "^\u{1b}[37mY\u{1b}[39m$", + "^\u{1b}[37mA\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mA\u{1b}[39m$", + "^\u{1b}[37mn\u{1b}[39m$", + "^\u{1b}[37md\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37mn\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m3\u{1b}[39m$", + "^\u{1b}[37m8\u{1b}[39m$", + "^\u{1b}[37m2\u{1b}[39m$", + "^\u{1b}[37m4\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m0\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m9\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mC\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37ml\u{1b}[39m$", + "^\u{1b}[37mc\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37mu\u{1b}[39m$", + "^\u{1b}[37mm\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mc\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37mr\u{1b}[39m$", + "^\u{1b}[37mb\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37mn\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$", + "^\u{1b}[37mt\u{1b}[39m$", + "^\u{1b}[37me\u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37m \u{1b}[39m$", + "^\u{1b}[37mC\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37ml\u{1b}[39m$", + "^\u{1b}[37mo\u{1b}[39m$", + "^\u{1b}[37mm\u{1b}[39m$", + "^\u{1b}[37mb\u{1b}[39m$", + "^\u{1b}[37mi\u{1b}[39m$", + "^\u{1b}[37ma\u{1b}[39m$" + ] + ) + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_2() { + let text = "\u{1b}[30mDebian\u{1b}[0m\u{1b}[31mDebian\u{1b}[0m\u{1b}[32mDebian\u{1b}[0m\u{1b}[33mDebian\u{1b}[0m\u{1b}[34mDebian\u{1b}[0m\u{1b}[35mDebian\u{1b}[0m\u{1b}[36mDebian\u{1b}[0m\u{1b}[37mDebian\u{1b}[0m\u{1b}[40mDebian\u{1b}[0m\u{1b}[41mDebian\u{1b}[0m\u{1b}[42mDebian\u{1b}[0m\u{1b}[43mDebian\u{1b}[0m\u{1b}[44mDebian\u{1b}[0m"; + assert_eq!( + chunks(text, 30, "", ""), + [ + "\u{1b}[30mDebian\u{1b}[39m\u{1b}[31mDebian\u{1b}[39m\u{1b}[32mDebian\u{1b}[39m\u{1b}[33mDebian\u{1b}[39m\u{1b}[34mDebian\u{1b}[39m", + "\u{1b}[35mDebian\u{1b}[39m\u{1b}[36mDebian\u{1b}[39m\u{1b}[37mDebian\u{1b}[39m\u{1b}[40mDebian\u{1b}[49m\u{1b}[41mDebian\u{1b}[49m", + "\u{1b}[42mDebian\u{1b}[49m\u{1b}[43mDebian\u{1b}[49m\u{1b}[44mDebian\u{1b}[49m", + ] + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_3() { + let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m"; + + assert_eq!( + chunks(text, 22, "", ""), + [ + "\u{1b}[37mCreate bytes from the \u{1b}[39m", + "\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m" + ] + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_3_keeping_words() { + let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m"; + + assert_eq!( + split_keeping_words(text, 22, "", ""), + "\u{1b}[37mCreate bytes from the \u{1b}[39m\n\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m " + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_4() { + let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m"; + + assert_eq!( + chunks(text, 10, "", ""), + [ + "\u{1b}[37mReturns th\u{1b}[39m", + "\u{1b}[37me floor of\u{1b}[39m", + "\u{1b}[37m a number \u{1b}[39m", + "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest i\u{1b}[39m", + "\u{1b}[37mnteger les\u{1b}[39m", + "\u{1b}[37ms than or \u{1b}[39m", + "\u{1b}[37mequal to t\u{1b}[39m", + "\u{1b}[37mhat number\u{1b}[39m", + "\u{1b}[37m).\u{1b}[39m", + ] + ); + } + + #[cfg(feature = "color")] + #[test] + fn chunks_wrap_4_keeping_words() { + let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m"; + assert_eq!( + split_keeping_words(text, 10, "", ""), + concat!( + "\u{1b}[37mReturns \u{1b}[39m \n", + "\u{1b}[37mthe floor \u{1b}[39m\n", + "\u{1b}[37mof a \u{1b}[39m \n", + "\u{1b}[37mnumber \u{1b}[39m \n", + "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m \n", + "\u{1b}[37minteger \u{1b}[39m \n", + "\u{1b}[37mless than \u{1b}[39m\n", + "\u{1b}[37mor equal \u{1b}[39m \n", + "\u{1b}[37mto that \u{1b}[39m \n", + "\u{1b}[37mnumber).\u{1b}[39m ", + ) + ); + } +} + +// \u{1b}[37mReturns \u{1b}[39m\n +// \u{1b}[37mthe floor \u{1b}[39m\n +// \u{1b}[37mof a \u{1b}[39m\n +// \u{1b}[37mnumber \u{1b}[39m\u{1b}[49m\n +// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n +// \u{1b}[37minteger \u{1b}[39m\n +// \u{1b}[37mless than \u{1b}[39m\n +// \u{1b}[37mor equal \u{1b}[39m\n +// \u{1b}[37mto that \u{1b}[39m\n +// \u{1b}[37mnumber).\u{1b}[39m " + +// +// + +// \u{1b}[37mReturns \u{1b}[39m\n +// \u{1b}[37mthe floor \u{1b}[39m\n +// \u{1b}[37mof a \u{1b}[39m\n +// \u{1b}[37mnumber \u{1b}[39m\u{1b}[49m\n +// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m\n +// \u{1b}[37minteger \u{1b}[39m\n +// \u{1b}[37mless than \u{1b}[39m\n +// \u{1b}[37mor equal \u{1b}[39m\n +// \u{1b}[37mto that \u{1b}[39m\n +// \u{1b}[37mnumber).\u{1b}[39m " + +// "\u{1b}[37mReturns\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mthe\u{1b}[37m floor\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mof\u{1b}[37m a\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mnumber\u{1b}[37m \u{1b}[39m\u{1b}[49m\n +// \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37minteger\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mless\u{1b}[37m than\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mor\u{1b}[37m equal\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mto\u{1b}[37m that\u{1b}[37m \u{1b}[39m\n +// \u{1b}[37mnumber).\u{1b}[39m " diff --git a/vendor/tabled/src/tabled.rs b/vendor/tabled/src/tabled.rs new file mode 100644 index 000000000..636173dc0 --- /dev/null +++ b/vendor/tabled/src/tabled.rs @@ -0,0 +1,150 @@ +use std::borrow::Cow; + +/// Tabled a trait responsible for providing a header fields and a row fields. +/// +/// It's urgent that `header` len is equal to `fields` len. +/// +/// ```text +/// Self::headers().len() == self.fields().len() +/// ``` +pub trait Tabled { + /// A length of fields and headers, + /// which must be the same. + const LENGTH: usize; + + /// Fields method must return a list of cells. + /// + /// The cells will be placed in the same row, preserving the order. + fn fields(&self) -> Vec<Cow<'_, str>>; + /// Headers must return a list of column names. + fn headers() -> Vec<Cow<'static, str>>; +} + +impl<T> Tabled for &T +where + T: Tabled, +{ + const LENGTH: usize = T::LENGTH; + + fn fields(&self) -> Vec<Cow<'_, str>> { + T::fields(self) + } + fn headers() -> Vec<Cow<'static, str>> { + T::headers() + } +} + +impl<T> Tabled for Box<T> +where + T: Tabled, +{ + const LENGTH: usize = T::LENGTH; + + fn fields(&self) -> Vec<Cow<'_, str>> { + T::fields(self) + } + fn headers() -> Vec<Cow<'static, str>> { + T::headers() + } +} + +macro_rules! tuple_table { + ( $($name:ident)+ ) => { + impl<$($name: Tabled),+> Tabled for ($($name,)+){ + const LENGTH: usize = $($name::LENGTH+)+ 0; + + fn fields(&self) -> Vec<Cow<'_, str>> { + #![allow(non_snake_case)] + let ($($name,)+) = self; + let mut fields = Vec::with_capacity(Self::LENGTH); + $(fields.append(&mut $name.fields());)+ + fields + } + + fn headers() -> Vec<Cow<'static, str>> { + let mut fields = Vec::with_capacity(Self::LENGTH); + $(fields.append(&mut $name::headers());)+ + fields + } + } + }; +} + +tuple_table! { A } +tuple_table! { A B } +tuple_table! { A B C } +tuple_table! { A B C D } +tuple_table! { A B C D E } +tuple_table! { A B C D E F } + +macro_rules! default_table { + ( $t:ty ) => { + impl Tabled for $t { + const LENGTH: usize = 1; + + fn fields(&self) -> Vec<Cow<'_, str>> { + vec![Cow::Owned(self.to_string())] + } + fn headers() -> Vec<Cow<'static, str>> { + vec![Cow::Borrowed(stringify!($t))] + } + } + }; + + ( $t:ty = borrowed ) => { + impl Tabled for $t { + const LENGTH: usize = 1; + + fn fields(&self) -> Vec<Cow<'_, str>> { + vec![Cow::Borrowed(self)] + } + fn headers() -> Vec<Cow<'static, str>> { + vec![Cow::Borrowed(stringify!($t))] + } + } + }; +} + +default_table!(&str = borrowed); +default_table!(str = borrowed); +default_table!(String); + +default_table!(char); + +default_table!(bool); + +default_table!(isize); +default_table!(usize); + +default_table!(u8); +default_table!(u16); +default_table!(u32); +default_table!(u64); +default_table!(u128); + +default_table!(i8); +default_table!(i16); +default_table!(i32); +default_table!(i64); +default_table!(i128); + +default_table!(f32); +default_table!(f64); + +impl<T, const N: usize> Tabled for [T; N] +where + T: std::fmt::Display, +{ + const LENGTH: usize = N; + + fn fields(&self) -> Vec<Cow<'_, str>> { + self.iter() + .map(ToString::to_string) + .map(Cow::Owned) + .collect() + } + + fn headers() -> Vec<Cow<'static, str>> { + (0..N).map(|i| Cow::Owned(format!("{i}"))).collect() + } +} diff --git a/vendor/tabled/src/tables/compact.rs b/vendor/tabled/src/tables/compact.rs new file mode 100644 index 000000000..14c1e2b5b --- /dev/null +++ b/vendor/tabled/src/tables/compact.rs @@ -0,0 +1,309 @@ +//! This module contains a [`CompactTable`] table. +//! +//! In contrast to [`Table`] [`CompactTable`] does no allocations but it consumes an iterator. +//! It's useful when you don't want to re/allocate a buffer for your data. +//! +//! # Example +//! +//! It works smoothly with arrays. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//!use tabled::{settings::Style, tables::CompactTable}; +//! +//! let data = [ +//! ["FreeBSD", "1993", "William and Lynne Jolitz", "?"], +//! ["OpenBSD", "1995", "Theo de Raadt", ""], +//! ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""], +//! ]; +//! +//! let table = CompactTable::from(data) +//! .with(Style::psql()) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! concat!( +//! " FreeBSD | 1993 | William and Lynne Jolitz | ? \n", +//! "-------------+------+------------------------------+---\n", +//! " OpenBSD | 1995 | Theo de Raadt | \n", +//! " HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | ", +//! ) +//! ); +//! ``` +//! +//! But it's default creation requires to be given an estimated cell width, and the amount of columns. +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//!use tabled::{settings::Style, tables::CompactTable}; +//! +//! let data = [ +//! ["FreeBSD", "1993", "William and Lynne Jolitz", "?"], +//! ["OpenBSD", "1995", "Theo de Raadt", ""], +//! ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""], +//! ]; +//! +//! // See what will happen if the given width is too narrow +//! +//! let table = CompactTable::new(&data) +//! .columns(4) +//! .width(5) +//! .with(Style::ascii()) +//! .to_string(); +//! +//! assert_eq!( +//! table, +//! "+-----+-----+-----+-----+\n\ +//! | FreeBSD | 1993 | William and Lynne Jolitz | ? |\n\ +//! |-----+-----+-----+-----|\n\ +//! | OpenBSD | 1995 | Theo de Raadt | |\n\ +//! |-----+-----+-----+-----|\n\ +//! | HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | |\n\ +//! +-----+-----+-----+-----+" +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use core::cmp::max; +use core::fmt; + +use crate::{ + grid::{ + config::{AlignmentHorizontal, CompactConfig, Indent, Sides}, + dimension::{ConstDimension, ConstSize, Dimension}, + records::{ + into_records::{LimitColumns, LimitRows}, + IntoRecords, IterRecords, + }, + util::string::string_width, + CompactGrid, + }, + settings::{Style, TableOption}, +}; + +/// A table which consumes an [`IntoRecords`] iterator. +/// It assumes that the content has only single line. +#[derive(Debug, Clone)] +pub struct CompactTable<I, D> { + records: I, + cfg: CompactConfig, + dims: D, + count_columns: usize, + count_rows: Option<usize>, +} + +impl<I> CompactTable<I, ConstDimension<0, 0>> { + /// Creates a new [`CompactTable`] structure with a width dimension for all columns. + pub const fn new(iter: I) -> Self + where + I: IntoRecords, + { + Self { + records: iter, + cfg: create_config(), + count_columns: 0, + count_rows: None, + dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)), + } + } +} + +impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> { + /// Set a height for each row. + pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>( + self, + size: S, + ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> { + let (width, _) = self.dims.into(); + CompactTable { + dims: ConstDimension::new(width, size.into()), + records: self.records, + cfg: self.cfg, + count_columns: self.count_columns, + count_rows: self.count_rows, + } + } + + /// Set a width for each column. + pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>( + self, + size: S, + ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> { + let (_, height) = self.dims.into(); + CompactTable { + dims: ConstDimension::new(size.into(), height), + records: self.records, + cfg: self.cfg, + count_columns: self.count_columns, + count_rows: self.count_rows, + } + } +} + +impl<I, D> CompactTable<I, D> { + /// Creates a new [`CompactTable`] structure with a known dimension. + /// + /// Notice that the function wont call [`Estimate`]. + /// + /// [`Estimate`]: crate::grid::dimension::Estimate + pub fn with_dimension(iter: I, dimension: D) -> Self + where + I: IntoRecords, + { + Self { + records: iter, + dims: dimension, + cfg: create_config(), + count_columns: 0, + count_rows: None, + } + } + + /// With is a generic function which applies options to the [`CompactTable`]. + pub fn with<O>(mut self, option: O) -> Self + where + for<'a> O: TableOption<IterRecords<&'a I>, D, CompactConfig>, + { + let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows); + option.change(&mut records, &mut self.cfg, &mut self.dims); + + self + } + + /// Limit a number of rows. + pub const fn rows(mut self, count_rows: usize) -> Self { + self.count_rows = Some(count_rows); + self + } + + /// Limit a number of columns. + pub const fn columns(mut self, count: usize) -> Self { + self.count_columns = count; + self + } + + /// Returns a table config. + pub fn get_config(&self) -> &CompactConfig { + &self.cfg + } + + /// Returns a table config. + pub fn get_config_mut(&mut self) -> &mut CompactConfig { + &mut self.cfg + } + + /// Format table into [fmt::Write]er. + pub fn fmt<W>(self, writer: W) -> fmt::Result + where + I: IntoRecords, + D: Dimension, + W: fmt::Write, + { + build_grid( + writer, + self.records, + self.dims, + self.cfg, + self.count_columns, + self.count_rows, + ) + } + + /// Format table into a writer. + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn build<W>(self, writer: W) -> std::io::Result<()> + where + I: IntoRecords, + D: Dimension, + W: std::io::Write, + { + let writer = super::util::utf8_writer::UTF8Writer::new(writer); + self.fmt(writer) + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) + } + + /// Build a string. + /// + /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. + #[allow(clippy::inherent_to_string)] + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn to_string(self) -> String + where + I: IntoRecords, + D: Dimension, + { + let mut buf = String::new(); + self.fmt(&mut buf).unwrap(); + buf + } +} + +impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]> + for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>> +where + T: AsRef<str>, +{ + fn from(mat: [[T; COLS]; ROWS]) -> Self { + let mut width = [0; COLS]; + for row in mat.iter() { + for (col, text) in row.iter().enumerate() { + let text = text.as_ref(); + let text_width = string_width(text); + width[col] = max(width[col], text_width); + } + } + + // add padding + for w in &mut width { + *w += 2; + } + + let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1)); + Self::with_dimension(mat, dims).columns(COLS).rows(ROWS) + } +} + +fn build_grid<W: fmt::Write, I: IntoRecords, D: Dimension>( + writer: W, + records: I, + dims: D, + config: CompactConfig, + cols: usize, + rows: Option<usize>, +) -> Result<(), fmt::Error> { + match rows { + Some(limit) => { + let records = LimitRows::new(records, limit); + let records = LimitColumns::new(records, cols); + let records = IterRecords::new(records, cols, rows); + CompactGrid::new(records, dims, config).build(writer) + } + None => { + let records = LimitColumns::new(records, cols); + let records = IterRecords::new(records, cols, rows); + CompactGrid::new(records, dims, config).build(writer) + } + } +} + +const fn create_config() -> CompactConfig { + CompactConfig::empty() + .set_padding(Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::zero(), + Indent::zero(), + )) + .set_alignment_horizontal(AlignmentHorizontal::Left) + .set_borders(*Style::ascii().get_borders()) +} + +impl<R, D> TableOption<R, D, CompactConfig> for CompactConfig { + fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { + *cfg = self; + } +} diff --git a/vendor/tabled/src/tables/extended.rs b/vendor/tabled/src/tables/extended.rs new file mode 100644 index 000000000..478505c4b --- /dev/null +++ b/vendor/tabled/src/tables/extended.rs @@ -0,0 +1,338 @@ +//! This module contains an [`ExtendedTable`] structure which is useful in cases where +//! a structure has a lot of fields. +//! +#![cfg_attr(feature = "derive", doc = "```")] +#![cfg_attr(not(feature = "derive"), doc = "```ignore")] +//! use tabled::{Tabled, tables::ExtendedTable}; +//! +//! #[derive(Tabled)] +//! struct Language { +//! name: &'static str, +//! designed_by: &'static str, +//! invented_year: usize, +//! } +//! +//! let languages = vec![ +//! Language{ +//! name: "C", +//! designed_by: "Dennis Ritchie", +//! invented_year: 1972 +//! }, +//! Language{ +//! name: "Rust", +//! designed_by: "Graydon Hoare", +//! invented_year: 2010 +//! }, +//! Language{ +//! name: "Go", +//! designed_by: "Rob Pike", +//! invented_year: 2009 +//! }, +//! ]; +//! +//! let table = ExtendedTable::new(languages).to_string(); +//! +//! let expected = "-[ RECORD 0 ]-+---------------\n\ +//! name | C\n\ +//! designed_by | Dennis Ritchie\n\ +//! invented_year | 1972\n\ +//! -[ RECORD 1 ]-+---------------\n\ +//! name | Rust\n\ +//! designed_by | Graydon Hoare\n\ +//! invented_year | 2010\n\ +//! -[ RECORD 2 ]-+---------------\n\ +//! name | Go\n\ +//! designed_by | Rob Pike\n\ +//! invented_year | 2009"; +//! +//! assert_eq!(table, expected); +//! ``` + +use std::borrow::Cow; +use std::fmt::{self, Display}; + +use crate::grid::util::string::string_width; +use crate::Tabled; + +/// `ExtendedTable` display data in a 'expanded display mode' from postgresql. +/// It may be useful for a large data sets with a lot of fields. +/// +/// See 'Examples' in <https://www.postgresql.org/docs/current/app-psql.html>. +/// +/// It escapes strings to resolve a multi-line ones. +/// Because of that ANSI sequences will be not be rendered too so colores will not be showed. +/// +/// ``` +/// use tabled::tables::ExtendedTable; +/// +/// let data = vec!["Hello", "2021"]; +/// let table = ExtendedTable::new(&data).to_string(); +/// +/// assert_eq!( +/// table, +/// concat!( +/// "-[ RECORD 0 ]-\n", +/// "&str | Hello\n", +/// "-[ RECORD 1 ]-\n", +/// "&str | 2021", +/// ) +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct ExtendedTable { + fields: Vec<String>, + records: Vec<Vec<String>>, +} + +impl ExtendedTable { + /// Creates a new instance of `ExtendedTable` + pub fn new<T>(iter: impl IntoIterator<Item = T>) -> Self + where + T: Tabled, + { + let data = iter + .into_iter() + .map(|i| { + i.fields() + .into_iter() + .map(|s| s.escape_debug().to_string()) + .collect() + }) + .collect(); + let header = T::headers() + .into_iter() + .map(|s| s.escape_debug().to_string()) + .collect(); + + Self { + records: data, + fields: header, + } + } + + /// Truncates table to a set width value for a table. + /// It returns a success inticator, where `false` means it's not possible to set the table width, + /// because of the given arguments. + /// + /// It tries to not affect fields, but if there's no enough space all records will be deleted and fields will be cut. + /// + /// The minimum width is 14. + pub fn truncate(&mut self, max: usize, suffix: &str) -> bool { + // -[ RECORD 0 ]- + let teplate_width = self.records.len().to_string().len() + 13; + let min_width = teplate_width; + if max < min_width { + return false; + } + + let suffix_width = string_width(suffix); + if max < suffix_width { + return false; + } + + let max = max - suffix_width; + + let fields_max_width = self + .fields + .iter() + .map(|s| string_width(s)) + .max() + .unwrap_or_default(); + + // 3 is a space for ' | ' + let fields_affected = max < fields_max_width + 3; + if fields_affected { + if max < 3 { + return false; + } + + let max = max - 3; + + if max < suffix_width { + return false; + } + + let max = max - suffix_width; + + truncate_fields(&mut self.fields, max, suffix); + truncate_records(&mut self.records, 0, suffix); + } else { + let max = max - fields_max_width - 3 - suffix_width; + truncate_records(&mut self.records, max, suffix); + } + + true + } +} + +impl From<Vec<Vec<String>>> for ExtendedTable { + fn from(mut data: Vec<Vec<String>>) -> Self { + if data.is_empty() { + return Self { + fields: vec![], + records: vec![], + }; + } + + let fields = data.remove(0); + + Self { + fields, + records: data, + } + } +} + +impl Display for ExtendedTable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.records.is_empty() { + return Ok(()); + } + + // It's possible that field|header can be a multiline string so + // we escape it and trim \" chars. + let fields = self.fields.iter().collect::<Vec<_>>(); + + let max_field_width = fields + .iter() + .map(|s| string_width(s)) + .max() + .unwrap_or_default(); + + let max_values_length = self + .records + .iter() + .map(|record| record.iter().map(|s| string_width(s)).max()) + .max() + .unwrap_or_default() + .unwrap_or_default(); + + for (i, records) in self.records.iter().enumerate() { + write_header_template(f, i, max_field_width, max_values_length)?; + + for (value, field) in records.iter().zip(fields.iter()) { + writeln!(f)?; + write_record(f, field, value, max_field_width)?; + } + + let is_last_record = i + 1 == self.records.len(); + if !is_last_record { + writeln!(f)?; + } + } + + Ok(()) + } +} + +fn truncate_records(records: &mut Vec<Vec<String>>, max_width: usize, suffix: &str) { + for fields in records { + truncate_fields(fields, max_width, suffix); + } +} + +fn truncate_fields(records: &mut Vec<String>, max_width: usize, suffix: &str) { + for text in records { + truncate(text, max_width, suffix); + } +} + +fn write_header_template( + f: &mut fmt::Formatter<'_>, + index: usize, + max_field_width: usize, + max_values_length: usize, +) -> fmt::Result { + let mut template = format!("-[ RECORD {index} ]-"); + let default_template_length = template.len(); + + // 3 - is responsible for ' | ' formatting + let max_line_width = std::cmp::max( + max_field_width + 3 + max_values_length, + default_template_length, + ); + let rest_to_print = max_line_width - default_template_length; + if rest_to_print > 0 { + // + 1 is a space after field name and we get a next pos so its +2 + if max_field_width + 2 > default_template_length { + let part1 = (max_field_width + 1) - default_template_length; + let part2 = rest_to_print - part1 - 1; + + template.extend( + std::iter::repeat('-') + .take(part1) + .chain(std::iter::once('+')) + .chain(std::iter::repeat('-').take(part2)), + ); + } else { + template.extend(std::iter::repeat('-').take(rest_to_print)); + } + } + + write!(f, "{template}")?; + + Ok(()) +} + +fn write_record( + f: &mut fmt::Formatter<'_>, + field: &str, + value: &str, + max_field_width: usize, +) -> fmt::Result { + write!(f, "{field:max_field_width$} | {value}") +} + +fn truncate(text: &mut String, max: usize, suffix: &str) { + let original_len = text.len(); + + if max == 0 || text.is_empty() { + *text = String::new(); + } else { + *text = cut_str_basic(text, max).into_owned(); + } + + let cut_was_done = text.len() < original_len; + if !suffix.is_empty() && cut_was_done { + text.push_str(suffix); + } +} + +fn cut_str_basic(s: &str, width: usize) -> Cow<'_, str> { + const REPLACEMENT: char = '\u{FFFD}'; + + let (length, count_unknowns, _) = split_at_pos(s, width); + let buf = &s[..length]; + if count_unknowns == 0 { + return Cow::Borrowed(buf); + } + + let mut buf = buf.to_owned(); + buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); + + Cow::Owned(buf) +} + +fn split_at_pos(s: &str, pos: usize) -> (usize, usize, usize) { + let mut length = 0; + let mut i = 0; + for c in s.chars() { + if i == pos { + break; + }; + + let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + + // We cut the chars which takes more then 1 symbol to display, + // in order to archive the necessary width. + if i + c_width > pos { + let count = pos - i; + return (length, count, c.len_utf8()); + } + + i += c_width; + length += c.len_utf8(); + } + + (length, 0, 0) +} diff --git a/vendor/tabled/src/tables/iter.rs b/vendor/tabled/src/tables/iter.rs new file mode 100644 index 000000000..af485ac08 --- /dev/null +++ b/vendor/tabled/src/tables/iter.rs @@ -0,0 +1,344 @@ +//! This module contains a [`IterTable`] table. +//! +//! In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator. +//! It's useful when you don't want to re/allocate a buffer for your data. +//! +//! # Example +//! +//! ``` +//! use tabled::{grid::records::IterRecords, tables::IterTable}; +//! +//! let iterator = vec![vec!["First", "row"], vec!["Second", "row"]]; +//! let records = IterRecords::new(iterator, 2, Some(2)); +//! let table = IterTable::new(records); +//! +//! let s = table.to_string(); +//! +//! assert_eq!( +//! s, +//! "+--------+-----+\n\ +//! | First | row |\n\ +//! +--------+-----+\n\ +//! | Second | row |\n\ +//! +--------+-----+", +//! ); +//! ``` +//! +//! [`Table`]: crate::Table + +use std::{fmt, io}; + +use crate::{ + grid::{ + colors::NoColors, + config::{AlignmentHorizontal, CompactConfig, Indent, Sides, SpannedConfig}, + dimension::{CompactGridDimension, DimensionValue, StaticDimension}, + records::{ + into_records::{ + truncate_records::ExactValue, BufColumns, BufRows, LimitColumns, LimitRows, + TruncateContent, + }, + IntoRecords, IterRecords, + }, + Grid, + }, + settings::{Style, TableOption}, +}; + +use super::util::utf8_writer::UTF8Writer; + +/// A table which consumes an [`IntoRecords`] iterator. +/// +/// To be able to build table we need a dimensions. +/// If no width and count_columns is set, [`IterTable`] will sniff the records, by +/// keeping a number of rows buffered (You can set the number via [`IterTable::sniff`]). +#[derive(Debug, Clone)] +pub struct IterTable<I> { + records: I, + cfg: CompactConfig, + table: Settings, +} + +#[derive(Debug, Clone)] +struct Settings { + sniff: usize, + count_columns: Option<usize>, + count_rows: Option<usize>, + width: Option<usize>, + height: Option<usize>, +} + +impl<I> IterTable<I> { + /// Creates a new [`IterTable`] structure. + pub fn new(iter: I) -> Self + where + I: IntoRecords, + { + Self { + records: iter, + cfg: create_config(), + table: Settings { + sniff: 1000, + count_columns: None, + count_rows: None, + height: None, + width: None, + }, + } + } + + /// With is a generic function which applies options to the [`IterTable`]. + pub fn with<O>(mut self, option: O) -> Self + where + for<'a> O: TableOption<IterRecords<&'a I>, StaticDimension, CompactConfig>, + { + let count_columns = self.table.count_columns.unwrap_or(0); + let mut records = IterRecords::new(&self.records, count_columns, self.table.count_rows); + let mut dims = StaticDimension::new(DimensionValue::Exact(0), DimensionValue::Exact(1)); + option.change(&mut records, &mut self.cfg, &mut dims); + + self + } + + /// Limit a number of columns. + pub fn columns(mut self, count_columns: usize) -> Self { + self.table.count_columns = Some(count_columns); + self + } + + /// Limit a number of rows. + pub fn rows(mut self, count_rows: usize) -> Self { + self.table.count_rows = Some(count_rows); + self + } + + /// Limit an amount of rows will be read for dimension estimations. + pub fn sniff(mut self, count: usize) -> Self { + self.table.sniff = count; + self + } + + /// Set a height for each row. + pub fn height(mut self, size: usize) -> Self { + self.table.height = Some(size); + self + } + + /// Set a width for each column. + pub fn width(mut self, size: usize) -> Self { + self.table.width = Some(size); + self + } + + /// Build a string. + /// + /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. + #[allow(clippy::inherent_to_string)] + pub fn to_string(self) -> String + where + I: IntoRecords, + { + let mut buf = String::new(); + self.fmt(&mut buf).expect("safe"); + + buf + } + + /// Format table into [`io::Write`]r. + pub fn build<W>(self, writer: W) -> io::Result<()> + where + I: IntoRecords, + W: io::Write, + { + let writer = UTF8Writer::new(writer); + self.fmt(writer) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) + } + + /// Format table into [fmt::Write]er. + pub fn fmt<W>(self, writer: W) -> fmt::Result + where + I: IntoRecords, + W: fmt::Write, + { + build_grid(writer, self.records, self.cfg, self.table) + } +} + +fn build_grid<W: fmt::Write, I: IntoRecords>( + f: W, + iter: I, + cfg: CompactConfig, + opts: Settings, +) -> fmt::Result { + let dont_sniff = opts.width.is_some() && opts.count_columns.is_some(); + if dont_sniff { + build_table_with_static_dims(f, iter, cfg, opts) + } else if opts.width.is_none() { + build_table_sniffing_with_unknown_width(f, iter, cfg, opts) + } else { + build_table_sniffing_with_known_width(f, iter, cfg, opts) + } +} + +fn build_table_with_static_dims<W, I>( + f: W, + iter: I, + cfg: CompactConfig, + opts: Settings, +) -> fmt::Result +where + W: fmt::Write, + I: IntoRecords, +{ + let count_columns = opts.count_columns.unwrap(); + let width = opts.width.unwrap(); + let height = opts.height.unwrap_or(1); + let contentw = ExactValue::Exact(width); + let pad = cfg.get_padding(); + let w = DimensionValue::Exact(width + pad.left.size + pad.right.size); + let h = DimensionValue::Exact(height + pad.top.size + pad.bottom.size); + let dims = StaticDimension::new(w, h); + let cfg = SpannedConfig::from(cfg); + + match opts.count_rows { + Some(limit) => { + let records = LimitRows::new(iter, limit); + let records = build_records(records, contentw, count_columns, Some(limit)); + Grid::new(records, dims, cfg, NoColors).build(f) + } + None => { + let records = build_records(iter, contentw, count_columns, None); + Grid::new(records, dims, cfg, NoColors).build(f) + } + } +} + +fn build_table_sniffing_with_unknown_width<W, I>( + f: W, + iter: I, + cfg: CompactConfig, + opts: Settings, +) -> fmt::Result +where + W: fmt::Write, + I: IntoRecords, +{ + let records = BufRows::new(iter, opts.sniff); + let records = BufColumns::from(records); + + let count_columns = get_count_columns(&opts, records.as_slice()); + + let (mut width, height) = { + let records = LimitColumns::new(records.as_slice(), count_columns); + let records = IterRecords::new(records, count_columns, None); + CompactGridDimension::dimension(records, &cfg) + }; + + let padding = cfg.get_padding(); + let pad = padding.left.size + padding.right.size; + let padv = padding.top.size + padding.bottom.size; + + if opts.sniff == 0 { + width = std::iter::repeat(pad) + .take(count_columns) + .collect::<Vec<_>>(); + } + + let content_width = ExactValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect()); + let dims_width = DimensionValue::List(width); + + let height_exact = opts.height.unwrap_or(1) + padv; + let mut dims_height = DimensionValue::Partial(height, height_exact); + + if opts.height.is_some() { + dims_height = DimensionValue::Exact(height_exact); + } + + let dims = StaticDimension::new(dims_width, dims_height); + let cfg = SpannedConfig::from(cfg); + + match opts.count_rows { + Some(limit) => { + let records = LimitRows::new(records, limit); + let records = build_records(records, content_width, count_columns, Some(limit)); + Grid::new(records, dims, cfg, NoColors).build(f) + } + None => { + let records = build_records(records, content_width, count_columns, None); + Grid::new(records, dims, cfg, NoColors).build(f) + } + } +} + +fn build_table_sniffing_with_known_width<W, I>( + f: W, + iter: I, + cfg: CompactConfig, + opts: Settings, +) -> fmt::Result +where + W: fmt::Write, + I: IntoRecords, +{ + let records = BufRows::new(iter, opts.sniff); + let records = BufColumns::from(records); + + let count_columns = get_count_columns(&opts, records.as_slice()); + + let width = opts.width.unwrap(); + let contentw = ExactValue::Exact(width); + + let padding = cfg.get_padding(); + let pad = padding.left.size + padding.right.size; + let padv = padding.top.size + padding.bottom.size; + + let height = opts.height.unwrap_or(1) + padv; + let dimsh = DimensionValue::Exact(height); + let dimsw = DimensionValue::Exact(width + pad); + let dims = StaticDimension::new(dimsw, dimsh); + + let cfg = SpannedConfig::from(cfg); + + match opts.count_rows { + Some(limit) => { + let records = LimitRows::new(records, limit); + let records = build_records(records, contentw, count_columns, Some(limit)); + Grid::new(records, dims, cfg, NoColors).build(f) + } + None => { + let records = build_records(records, contentw, count_columns, None); + Grid::new(records, dims, cfg, NoColors).build(f) + } + } +} + +fn get_count_columns(opts: &Settings, buf: &[Vec<String>]) -> usize { + match opts.count_columns { + Some(size) => size, + None => buf.iter().map(|row| row.len()).max().unwrap_or(0), + } +} + +fn create_config() -> CompactConfig { + CompactConfig::default() + .set_padding(Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::default(), + Indent::default(), + )) + .set_alignment_horizontal(AlignmentHorizontal::Left) + .set_borders(*Style::ascii().get_borders()) +} + +fn build_records<I: IntoRecords>( + records: I, + width: ExactValue<'_>, + count_columns: usize, + count_rows: Option<usize>, +) -> IterRecords<LimitColumns<TruncateContent<'_, I>>> { + let records = TruncateContent::new(records, width); + let records = LimitColumns::new(records, count_columns); + IterRecords::new(records, count_columns, count_rows) +} diff --git a/vendor/tabled/src/tables/mod.rs b/vendor/tabled/src/tables/mod.rs new file mode 100644 index 000000000..e0c4bf794 --- /dev/null +++ b/vendor/tabled/src/tables/mod.rs @@ -0,0 +1,48 @@ +//! Module contains a list of table representatives. +//! +//! ## [`Table`] +//! +//! A default table implementation. +//! +//! ## [`IterTable`] +//! +//! Just like [`Table`] but it's API is a bit different to serve better in context +//! where there is a memory limit. +//! +//! ## [`ExtendedTable`] +//! +//! It's a table which is useful for large amount of data. +//! +//! ## [`PoolTable`] +//! +//! A table with a greather controll of a layout. + +mod compact; +mod util; + +#[cfg(feature = "std")] +mod extended; +#[cfg(feature = "std")] +mod iter; +#[cfg(feature = "std")] +mod table; +#[cfg(feature = "std")] +mod table_pool; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use table::Table; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use iter::IterTable; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use extended::ExtendedTable; + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub use table_pool::{PoolTable, TableValue}; + +pub use compact::CompactTable; diff --git a/vendor/tabled/src/tables/table.rs b/vendor/tabled/src/tables/table.rs new file mode 100644 index 000000000..d6ff37661 --- /dev/null +++ b/vendor/tabled/src/tables/table.rs @@ -0,0 +1,464 @@ +//! This module contains a main table representation [`Table`]. + +use core::ops::DerefMut; +use std::{borrow::Cow, fmt, iter::FromIterator}; + +use crate::{ + builder::Builder, + grid::{ + colors::NoColors, + config::{ + AlignmentHorizontal, ColorMap, ColoredConfig, CompactConfig, Entity, Formatting, + Indent, Sides, SpannedConfig, + }, + dimension::{CompleteDimensionVecRecords, Dimension, Estimate, PeekableDimension}, + records::{ + vec_records::{CellInfo, VecRecords}, + ExactRecords, Records, + }, + PeekableGrid, + }, + settings::{Style, TableOption}, + Tabled, +}; + +/// The structure provides an interface for building a table for types that implements [`Tabled`]. +/// +/// To build a string representation of a table you must use a [`std::fmt::Display`]. +/// Or simply call `.to_string()` method. +/// +/// The default table [`Style`] is [`Style::ascii`], +/// with a 1 left and right [`Padding`]. +/// +/// ## Example +/// +/// ### Basic usage +/// +/// ```rust,no_run +/// use tabled::Table; +/// +/// let table = Table::new(&["Year", "2021"]); +/// ``` +/// +/// ### With settings +/// +/// ```rust,no_run +/// use tabled::{Table, settings::{Style, Alignment}}; +/// +/// let data = vec!["Hello", "2021"]; +/// let mut table = Table::new(&data); +/// table.with(Style::psql()).with(Alignment::left()); +/// +/// println!("{}", table); +/// ``` +/// +/// [`Padding`]: crate::settings::Padding +/// [`Style`]: crate::settings::Style +/// [`Style::ascii`]: crate::settings::Style::ascii +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Table { + records: VecRecords<CellInfo<String>>, + config: ColoredConfig, + dimension: CompleteDimensionVecRecords<'static>, +} + +impl Table { + /// New creates a Table instance. + /// + /// If you use a reference iterator you'd better use [`FromIterator`] instead. + /// As it has a different lifetime constraints and make less copies therefore. + pub fn new<I, T>(iter: I) -> Self + where + I: IntoIterator<Item = T>, + T: Tabled, + { + let mut header = Vec::with_capacity(T::LENGTH); + for text in T::headers() { + let text = text.into_owned(); + let cell = CellInfo::new(text); + header.push(cell); + } + + let mut records = vec![header]; + for row in iter.into_iter() { + let mut list = Vec::with_capacity(T::LENGTH); + for text in row.fields().into_iter() { + let text = text.into_owned(); + let cell = CellInfo::new(text); + + list.push(cell); + } + + records.push(list); + } + + let records = VecRecords::new(records); + + Self { + records, + config: ColoredConfig::new(configure_grid()), + dimension: CompleteDimensionVecRecords::default(), + } + } + + /// Creates a builder from a data set given. + /// + /// # Example + /// + /// + #[cfg_attr(feature = "derive", doc = "```")] + #[cfg_attr(not(feature = "derive"), doc = "```ignore")] + /// use tabled::{ + /// Table, Tabled, + /// settings::{object::Segment, Modify, Alignment} + /// }; + /// + /// #[derive(Tabled)] + /// struct User { + /// name: &'static str, + /// #[tabled(inline("device::"))] + /// device: Device, + /// } + /// + /// #[derive(Tabled)] + /// enum Device { + /// PC, + /// Mobile + /// } + /// + /// let data = vec![ + /// User { name: "Vlad", device: Device::Mobile }, + /// User { name: "Dimitry", device: Device::PC }, + /// User { name: "John", device: Device::PC }, + /// ]; + /// + /// let mut table = Table::builder(data) + /// .index() + /// .column(0) + /// .transpose() + /// .build() + /// .with(Modify::new(Segment::new(1.., 1..)).with(Alignment::center())) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "+----------------+------+---------+------+\n\ + /// | name | Vlad | Dimitry | John |\n\ + /// +----------------+------+---------+------+\n\ + /// | device::PC | | + | + |\n\ + /// +----------------+------+---------+------+\n\ + /// | device::Mobile | + | | |\n\ + /// +----------------+------+---------+------+" + /// ) + /// ``` + pub fn builder<I, T>(iter: I) -> Builder + where + T: Tabled, + I: IntoIterator<Item = T>, + { + let mut records = Vec::new(); + for row in iter { + let mut list = Vec::with_capacity(T::LENGTH); + for text in row.fields().into_iter() { + list.push(text.into_owned()); + } + + records.push(list); + } + + let mut b = Builder::from(records); + let _ = b.set_header(T::headers()).hint_column_size(T::LENGTH); + + b + } + + /// With is a generic function which applies options to the [`Table`]. + /// + /// It applies settings immediately. + pub fn with<O>(&mut self, option: O) -> &mut Self + where + O: TableOption< + VecRecords<CellInfo<String>>, + CompleteDimensionVecRecords<'static>, + ColoredConfig, + >, + { + self.dimension.clear_width(); + self.dimension.clear_height(); + + option.change(&mut self.records, &mut self.config, &mut self.dimension); + + self + } + + /// Returns a table shape (count rows, count columns). + pub fn shape(&self) -> (usize, usize) { + (self.count_rows(), self.count_columns()) + } + + /// Returns an amount of rows in the table. + pub fn count_rows(&self) -> usize { + self.records.count_rows() + } + + /// Returns an amount of columns in the table. + pub fn count_columns(&self) -> usize { + self.records.count_columns() + } + + /// Returns a table shape (count rows, count columns). + pub fn is_empty(&self) -> bool { + let (count_rows, count_cols) = self.shape(); + count_rows == 0 || count_cols == 0 + } + + /// Returns total widths of a table, including margin and horizontal lines. + pub fn total_height(&self) -> usize { + let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); + dims.estimate(&self.records, self.config.as_ref()); + + let total = (0..self.count_rows()) + .map(|row| dims.get_height(row)) + .sum::<usize>(); + let counth = self.config.count_horizontal(self.count_rows()); + + let margin = self.config.get_margin(); + + total + counth + margin.top.size + margin.bottom.size + } + + /// Returns total widths of a table, including margin and vertical lines. + pub fn total_width(&self) -> usize { + let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); + dims.estimate(&self.records, self.config.as_ref()); + + let total = (0..self.count_columns()) + .map(|col| dims.get_width(col)) + .sum::<usize>(); + let countv = self.config.count_vertical(self.count_columns()); + + let margin = self.config.get_margin(); + + total + countv + margin.left.size + margin.right.size + } + + /// Returns a table config. + pub fn get_config(&self) -> &ColoredConfig { + &self.config + } + + /// Returns a table config. + pub fn get_config_mut(&mut self) -> &mut ColoredConfig { + &mut self.config + } + + /// Returns a used records. + pub fn get_records(&self) -> &VecRecords<CellInfo<String>> { + &self.records + } + + /// Returns a used records. + pub fn get_records_mut(&mut self) -> &mut VecRecords<CellInfo<String>> { + &mut self.records + } +} + +impl Default for Table { + fn default() -> Self { + Self { + records: VecRecords::default(), + config: ColoredConfig::new(configure_grid()), + dimension: CompleteDimensionVecRecords::default(), + } + } +} + +impl fmt::Display for Table { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_empty() { + return Ok(()); + } + + let config = use_format_configuration(f, self); + let colors = self.config.get_colors(); + + if !self.dimension.is_empty() { + let mut dims = self.dimension.clone(); + dims.estimate(&self.records, config.as_ref()); + + print_grid(f, &self.records, &config, &dims, colors) + } else { + let mut dims = PeekableDimension::default(); + dims.estimate(&self.records, &config); + + print_grid(f, &self.records, &config, &dims, colors) + } + } +} + +impl<T, V> FromIterator<T> for Table +where + T: IntoIterator<Item = V>, + V: Into<String>, +{ + fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { + Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build() + } +} + +impl From<Builder> for Table { + fn from(builder: Builder) -> Self { + let data: Vec<Vec<CellInfo<String>>> = builder.into(); + let records = VecRecords::new(data); + + Self { + records, + config: ColoredConfig::new(configure_grid()), + dimension: CompleteDimensionVecRecords::default(), + } + } +} + +impl From<Table> for Builder { + fn from(val: Table) -> Self { + let count_columns = val.count_columns(); + let data: Vec<Vec<CellInfo<String>>> = val.records.into(); + let mut builder = Builder::from(data); + let _ = builder.hint_column_size(count_columns); + builder + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for CompactConfig { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + *cfg.deref_mut() = self.into(); + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for ColoredConfig { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + *cfg = self; + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for SpannedConfig { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + *cfg.deref_mut() = self; + } +} + +impl<R, D> TableOption<R, D, ColoredConfig> for &SpannedConfig { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + *cfg.deref_mut() = self.clone(); + } +} + +fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal { + match alignment { + fmt::Alignment::Left => AlignmentHorizontal::Left, + fmt::Alignment::Right => AlignmentHorizontal::Right, + fmt::Alignment::Center => AlignmentHorizontal::Center, + } +} + +fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) { + match alignment { + fmt::Alignment::Left => (available, 0), + fmt::Alignment::Right => (0, available), + fmt::Alignment::Center => { + let left = available / 2; + let right = available - left; + (left, right) + } + } +} + +fn configure_grid() -> SpannedConfig { + let mut cfg = SpannedConfig::default(); + cfg.set_padding( + Entity::Global, + Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::default(), + Indent::default(), + ), + ); + cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Left); + cfg.set_formatting(Entity::Global, Formatting::new(false, false, false)); + cfg.set_borders(*Style::ascii().get_borders()); + + cfg +} + +fn use_format_configuration<'a>( + f: &mut fmt::Formatter<'_>, + table: &'a Table, +) -> Cow<'a, SpannedConfig> { + if f.align().is_some() || f.width().is_some() { + let mut cfg = table.config.as_ref().clone(); + + set_align_table(f, &mut cfg); + set_width_table(f, &mut cfg, table); + + Cow::Owned(cfg) + } else { + Cow::Borrowed(table.config.as_ref()) + } +} + +fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) { + if let Some(alignment) = f.align() { + let alignment = convert_fmt_alignment(alignment); + cfg.set_alignment_horizontal(Entity::Global, alignment); + } +} + +fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) { + if let Some(width) = f.width() { + let total_width = table.total_width(); + if total_width >= width { + return; + } + + let mut fill = f.fill(); + if fill == char::default() { + fill = ' '; + } + + let available = width - total_width; + let alignment = f.align().unwrap_or(fmt::Alignment::Left); + let (left, right) = table_padding(alignment, available); + + let mut margin = cfg.get_margin(); + margin.left.size += left; + margin.right.size += right; + + if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default() + { + margin.left.fill = fill; + } + + if (margin.right.size > 0 && margin.right.fill == char::default()) + || fill != char::default() + { + margin.right.fill = fill; + } + + cfg.set_margin(margin); + } +} + +fn print_grid<F: fmt::Write, D: Dimension>( + f: &mut F, + records: &VecRecords<CellInfo<String>>, + cfg: &SpannedConfig, + dims: D, + colors: &ColorMap, +) -> fmt::Result { + if !colors.is_empty() { + PeekableGrid::new(records, cfg, &dims, colors).build(f) + } else { + PeekableGrid::new(records, cfg, &dims, NoColors).build(f) + } +} diff --git a/vendor/tabled/src/tables/table_pool.rs b/vendor/tabled/src/tables/table_pool.rs new file mode 100644 index 000000000..07d2a5437 --- /dev/null +++ b/vendor/tabled/src/tables/table_pool.rs @@ -0,0 +1,1607 @@ +use core::fmt::{self, Display, Formatter}; + +use crate::{ + grid::{ + config::{AlignmentHorizontal, CompactMultilineConfig, Indent, Sides}, + dimension::{DimensionPriority, PoolTableDimension}, + records::EmptyRecords, + records::IntoRecords, + }, + settings::{Style, TableOption}, +}; + +/// [`PoolTable`] is a table which allows a greater set of possibilities for cell alignment. +/// It's data is not aligned in any way by default. +/// +/// It works similar to the main [`Table`] by default. +/// +/// +/// ``` +/// use tabled::tables::PoolTable; +/// +/// let data = vec![ +/// vec!["Hello", "World", "!"], +/// vec!["Salve", "mondo", "!"], +/// vec!["Hola", "mundo", "!"], +/// ]; +/// +/// let table = PoolTable::new(data).to_string(); +/// +/// assert_eq!( +/// table, +/// "+-------+-------+---+\n\ +/// | Hello | World | ! |\n\ +/// +-------+-------+---+\n\ +/// | Salve | mondo | ! |\n\ +/// +-------+-------+---+\n\ +/// | Hola | mundo | ! |\n\ +/// +-------+-------+---+" +/// ) +/// ``` +/// +/// But it allows you to have a different number of columns inside the rows. +/// +/// ``` +/// use tabled::tables::PoolTable; +/// +/// let data = vec![ +/// vec!["Hello", "World", "!"], +/// vec!["Salve, mondo!"], +/// vec!["Hola", "mundo", "", "", "!"], +/// ]; +/// +/// let table = PoolTable::new(data).to_string(); +/// +/// assert_eq!( +/// table, +/// "+---------+---------+----+\n\ +/// | Hello | World | ! |\n\ +/// +---------+---------+----+\n\ +/// | Salve, mondo! |\n\ +/// +------+-------+--+--+---+\n\ +/// | Hola | mundo | | | ! |\n\ +/// +------+-------+--+--+---+" +/// ) +/// ``` +/// +/// Notice that you also can build a custom table layout by using [`TableValue`]. +/// +/// ``` +/// use tabled::tables::{PoolTable, TableValue}; +/// +/// let message = "Hello\nWorld"; +/// +/// let data = TableValue::Column(vec![ +/// TableValue::Row(vec![ +/// TableValue::Column(vec![ +/// TableValue::Cell(String::from(message)), +/// ]), +/// TableValue::Column(vec![ +/// TableValue::Cell(String::from(message)), +/// TableValue::Row(vec![ +/// TableValue::Cell(String::from(message)), +/// TableValue::Cell(String::from(message)), +/// TableValue::Cell(String::from(message)), +/// ]) +/// ]), +/// ]), +/// TableValue::Cell(String::from(message)), +/// ]); +/// +/// let table = PoolTable::from(data).to_string(); +/// +/// assert_eq!( +/// table, +/// "+-------+-----------------------+\n\ +/// | Hello | Hello |\n\ +/// | World | World |\n\ +/// | +-------+-------+-------+\n\ +/// | | Hello | Hello | Hello |\n\ +/// | | World | World | World |\n\ +/// +-------+-------+-------+-------+\n\ +/// | Hello |\n\ +/// | World |\n\ +/// +-------------------------------+" +/// ) +/// ``` +/// +/// [`Table`]: crate::Table +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PoolTable { + config: CompactMultilineConfig, + dims: PoolTableDimension, + value: TableValue, +} + +impl PoolTable { + /// Creates a [`PoolTable`] out from a record iterator. + pub fn new<I: IntoRecords>(iter: I) -> Self { + let value = TableValue::Column( + iter.iter_rows() + .into_iter() + .map(|row| { + TableValue::Row( + row.into_iter() + .map(|cell| cell.as_ref().to_string()) + .map(TableValue::Cell) + .collect(), + ) + }) + .collect(), + ); + + Self { + config: configure_grid(), + dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List), + value, + } + } + + /// A is a generic function which applies options to the [`PoolTable`] configuration. + /// + /// Notice that it has a limited support of options. + /// + /// ``` + /// use tabled::tables::PoolTable; + /// use tabled::settings::{Style, Padding}; + /// + /// let data = vec![ + /// vec!["Hello", "World", "!"], + /// vec!["Salve", "mondo", "!"], + /// vec!["Hola", "mundo", "!"], + /// ]; + /// + /// let table = PoolTable::new(data) + /// .with(Style::extended()) + /// .with(Padding::zero()) + /// .to_string(); + /// + /// assert_eq!( + /// table, + /// "╔═════╦═════╦═╗\n\ + /// ║Hello║World║!║\n\ + /// ╠═════╬═════╬═╣\n\ + /// ║Salve║mondo║!║\n\ + /// ╠═════╬═════╬═╣\n\ + /// ║Hola ║mundo║!║\n\ + /// ╚═════╩═════╩═╝" + /// ) + /// ``` + pub fn with<O>(&mut self, option: O) -> &mut Self + where + O: TableOption<EmptyRecords, PoolTableDimension, CompactMultilineConfig>, + { + let mut records = EmptyRecords::default(); + option.change(&mut records, &mut self.config, &mut self.dims); + + self + } +} + +impl From<TableValue> for PoolTable { + fn from(value: TableValue) -> Self { + Self { + config: configure_grid(), + dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List), + value, + } + } +} + +impl Display for PoolTable { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + print::build_table(&self.value, &self.config, self.dims).fmt(f) + } +} + +/// [`TableValue`] a structure which is responsible for a [`PoolTable`] layout. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum TableValue { + /// A horizontal row. + Row(Vec<TableValue>), + /// A vertical column. + Column(Vec<TableValue>), + /// A single cell. + Cell(String), +} + +fn configure_grid() -> CompactMultilineConfig { + let pad = Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::default(), + Indent::default(), + ); + + CompactMultilineConfig::default() + .set_padding(pad) + .set_alignment_horizontal(AlignmentHorizontal::Left) + .set_borders(*Style::ascii().get_borders()) +} + +impl<R, C> TableOption<R, PoolTableDimension, C> for PoolTableDimension { + fn change(self, _: &mut R, _: &mut C, dimension: &mut PoolTableDimension) { + *dimension = self; + } +} + +impl<R, D> TableOption<R, D, CompactMultilineConfig> for CompactMultilineConfig { + fn change(self, _: &mut R, config: &mut CompactMultilineConfig, _: &mut D) { + *config = self; + } +} + +mod print { + use std::{cmp::max, collections::HashMap, iter::repeat}; + + use papergrid::{ + color::StaticColor, + config::{Border, Borders}, + util::string::string_width_multiline, + }; + + use crate::{ + builder::Builder, + grid::{ + config::{ + AlignmentHorizontal, AlignmentVertical, ColoredConfig, CompactMultilineConfig, + Indent, Offset, Sides, + }, + dimension::{Dimension, DimensionPriority, Estimate, PoolTableDimension}, + records::Records, + util::string::{count_lines, get_lines, string_dimension, string_width}, + }, + settings::{Padding, Style, TableOption}, + }; + + use super::TableValue; + + #[derive(Debug, Default)] + struct PrintContext { + pos: usize, + is_last_col: bool, + is_last_row: bool, + is_first_col: bool, + is_first_row: bool, + kv: bool, + kv_is_first: bool, + list: bool, + list_is_first: bool, + no_left: bool, + no_right: bool, + no_bottom: bool, + lean_top: bool, + top_intersection: bool, + top_left: bool, + intersections_horizontal: Vec<usize>, + intersections_vertical: Vec<usize>, + size: Dim, + } + + struct CellData { + content: String, + intersections_horizontal: Vec<usize>, + intersections_vertical: Vec<usize>, + } + + impl CellData { + fn new(content: String, i_horizontal: Vec<usize>, i_vertical: Vec<usize>) -> Self { + Self { + content, + intersections_horizontal: i_horizontal, + intersections_vertical: i_vertical, + } + } + } + + pub(super) fn build_table( + val: &TableValue, + cfg: &CompactMultilineConfig, + dims_priority: PoolTableDimension, + ) -> String { + let dims = collect_table_dimensions(val, cfg); + let ctx = PrintContext { + is_last_col: true, + is_last_row: true, + is_first_col: true, + is_first_row: true, + size: *dims.all.get(&0).unwrap(), + ..Default::default() + }; + + let data = _build_table(val, cfg, &dims, dims_priority, ctx); + let mut table = data.content; + + let margin = cfg.get_margin(); + let has_margin = margin.top.size > 0 + || margin.bottom.size > 0 + || margin.left.size > 0 + || margin.right.size > 0; + if has_margin { + let color = convert_border_colors(cfg.get_margin_color()); + table = set_margin(&table, *margin, color); + } + + table + } + + fn _build_table( + val: &TableValue, + cfg: &CompactMultilineConfig, + dims: &Dimensions, + priority: PoolTableDimension, + ctx: PrintContext, + ) -> CellData { + match val { + TableValue::Cell(text) => generate_value_cell(text, cfg, ctx), + TableValue::Row(list) => { + if list.is_empty() { + return generate_value_cell("", cfg, ctx); + } + + generate_table_row(list, cfg, dims, priority, ctx) + } + TableValue::Column(list) => { + if list.is_empty() { + return generate_value_cell("", cfg, ctx); + } + + generate_table_column(list, cfg, dims, priority, ctx) + } + } + } + + fn generate_table_column( + list: &Vec<TableValue>, + cfg: &CompactMultilineConfig, + dims: &Dimensions, + priority: PoolTableDimension, + ctx: PrintContext, + ) -> CellData { + let array_dims = dims.arrays.get(&ctx.pos).unwrap(); + + let height = dims.all.get(&ctx.pos).unwrap().height; + let additional_height = ctx.size.height - height; + let (chunk_height, mut rest_height) = split_value(additional_height, list.len()); + + let mut intersections_horizontal = ctx.intersections_horizontal; + let mut intersections_vertical = ctx.intersections_vertical; + let mut next_vsplit = false; + let mut next_intersections_vertical = vec![]; + + let mut builder = Builder::new(); + for (i, val) in list.iter().enumerate() { + let val_pos = *array_dims.index.get(&i).unwrap(); + + let mut height = dims.all.get(&val_pos).unwrap().height; + match priority.height() { + DimensionPriority::First => { + if i == 0 { + height += additional_height; + } + } + DimensionPriority::Last => { + if i + 1 == list.len() { + height += additional_height; + } + } + DimensionPriority::List => { + height += chunk_height; + + if rest_height > 0 { + height += 1; + rest_height -= 1; // must be safe + } + } + } + + let size = Dim::new(ctx.size.width, height); + + let (split, intersections_vertical) = + short_splits3(&mut intersections_vertical, size.height); + let old_split = next_vsplit; + next_vsplit = split; + + let is_prev_list_not_first = ctx.list && !ctx.list_is_first; + let valctx = PrintContext { + pos: val_pos, + is_last_col: ctx.is_last_col, + is_last_row: ctx.is_last_row && i + 1 == list.len(), + is_first_col: ctx.is_first_col, + is_first_row: ctx.is_first_row && i == 0, + kv: ctx.kv, + kv_is_first: ctx.kv_is_first, + list: true, + list_is_first: i == 0 && !is_prev_list_not_first, + no_left: ctx.no_left, + no_right: ctx.no_right, + no_bottom: ctx.no_bottom && i + 1 == list.len(), + lean_top: ctx.lean_top && i == 0, + top_intersection: (ctx.top_intersection && i == 0) || old_split, + top_left: ctx.top_left || i > 0, + intersections_horizontal, + intersections_vertical, + size, + }; + + let data = _build_table(val, cfg, dims, priority, valctx); + intersections_horizontal = data.intersections_horizontal; + next_intersections_vertical.extend(data.intersections_vertical); + + let _ = builder.push_record([data.content]); + } + + let table = builder + .build() + .with(Style::empty()) + .with(Padding::zero()) + .to_string(); + + CellData::new(table, intersections_horizontal, next_intersections_vertical) + } + + fn generate_table_row( + list: &Vec<TableValue>, + cfg: &CompactMultilineConfig, + dims: &Dimensions, + priority: PoolTableDimension, + ctx: PrintContext, + ) -> CellData { + let array_dims = dims.arrays.get(&ctx.pos).unwrap(); + + let list_width = dims.all.get(&ctx.pos).unwrap().width; + let additional_width = ctx.size.width - list_width; + let (chunk_width, mut rest_width) = split_value(additional_width, list.len()); + + let mut intersections_horizontal = ctx.intersections_horizontal; + let mut intersections_vertical = ctx.intersections_vertical; + let mut new_intersections_horizontal = vec![]; + let mut split_next = false; + + let mut buf = Vec::with_capacity(list.len()); + for (i, val) in list.iter().enumerate() { + let val_pos = *array_dims.index.get(&i).unwrap(); + + let mut width = dims.all.get(&val_pos).unwrap().width; + match priority.width() { + DimensionPriority::First => { + if i == 0 { + width += additional_width; + } + } + DimensionPriority::Last => { + if i + 1 == list.len() { + width += additional_width; + } + } + DimensionPriority::List => { + width += chunk_width; + + if rest_width > 0 { + width += 1; + rest_width -= 1; // must be safe + } + } + } + + let size = Dim::new(width, ctx.size.height); + + let (split, intersections_horizontal) = + short_splits3(&mut intersections_horizontal, width); + let old_split = split_next; + split_next = split; + + let is_prev_list_not_first = ctx.list && !ctx.list_is_first; + let valctx = PrintContext { + pos: val_pos, + is_first_col: ctx.is_first_col && i == 0, + is_last_col: ctx.is_last_col && i + 1 == list.len(), + is_last_row: ctx.is_last_row, + is_first_row: ctx.is_first_row, + kv: false, + kv_is_first: false, + list: false, + list_is_first: !is_prev_list_not_first, + no_left: false, + no_right: !(ctx.is_last_col && i + 1 == list.len()), + no_bottom: false, + lean_top: !(ctx.is_first_col && i == 0), + top_intersection: (ctx.top_intersection && i == 0) || old_split, + top_left: ctx.top_left && i == 0, + intersections_horizontal, + intersections_vertical, + size, + }; + + let val = _build_table(val, cfg, dims, priority, valctx); + intersections_vertical = val.intersections_vertical; + new_intersections_horizontal.extend(val.intersections_horizontal.iter()); + let value = val.content; + + buf.push(value); + } + + let mut b = Builder::with_capacity(1); + let _ = b.hint_column_size(buf.len()).push_record(buf); + let table = b + .build() + .with(Style::empty()) + .with(Padding::zero()) + .to_string(); + + CellData::new(table, new_intersections_horizontal, intersections_vertical) + } + + fn generate_value_cell( + text: &str, + cfg: &CompactMultilineConfig, + ctx: PrintContext, + ) -> CellData { + let width = ctx.size.width; + let height = ctx.size.height; + let table = generate_value_table(text, cfg, ctx); + CellData::new(table, vec![width], vec![height]) + } + + fn generate_value_table( + text: &str, + cfg: &CompactMultilineConfig, + mut ctx: PrintContext, + ) -> String { + if ctx.size.width == 0 || ctx.size.height == 0 { + return String::new(); + } + + let halignment = cfg.get_alignment_horizontal(); + let valignment = cfg.get_alignment_vertical(); + let pad = cfg.get_padding(); + let pad_color = cfg.get_padding_color(); + let pad_color = convert_border_colors(pad_color); + let lines_alignemnt = cfg.get_formatting().allow_lines_alignment; + + let mut borders = *cfg.get_borders(); + + let bottom_intesection = cfg.get_borders().bottom_intersection.unwrap_or(' '); + let mut horizontal_splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width); + squash_splits(&mut horizontal_splits); + + let right_intersection = borders.right_intersection.unwrap_or(' '); + let mut vertical_splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height); + squash_splits(&mut vertical_splits); + + config_borders(&mut borders, &ctx); + let border = create_border(borders); + + let borders_colors = *cfg.get_borders_color(); + let border_color = create_border(borders_colors); + + let mut height = ctx.size.height; + height -= pad.top.size + pad.bottom.size; + + let mut width = ctx.size.width; + width -= pad.left.size + pad.right.size; + + let count_lines = count_lines(text); + let (top, bottom) = indent_vertical(valignment, height, count_lines); + + let mut buf = String::new(); + print_top_line( + &mut buf, + border, + border_color, + &horizontal_splits, + bottom_intesection, + ctx.size.width, + ); + + let mut line_index = 0; + let mut vertical_splits = &vertical_splits[..]; + + for _ in 0..top { + let mut border = border; + if vertical_splits.first() == Some(&line_index) { + border.left = Some(right_intersection); + vertical_splits = &vertical_splits[1..]; + } + + print_line(&mut buf, border, border_color, None, ' ', ctx.size.width); + line_index += 1; + } + + for _ in 0..pad.top.size { + let mut border = border; + if vertical_splits.first() == Some(&line_index) { + border.left = Some(right_intersection); + vertical_splits = &vertical_splits[1..]; + } + + print_line( + &mut buf, + border, + border_color, + pad_color.top, + pad.top.fill, + ctx.size.width, + ); + line_index += 1; + } + + if lines_alignemnt { + for line in get_lines(text) { + let line_width = string_width(&line); + let (left, right) = indent_horizontal(halignment, width, line_width); + + if border.has_left() { + let mut c = border.left.unwrap_or(' '); + if vertical_splits.first() == Some(&line_index) { + c = right_intersection; + vertical_splits = &vertical_splits[1..]; + } + + print_char(&mut buf, c, border_color.left); + } + + print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size); + buf.extend(repeat(' ').take(left)); + buf.push_str(&line); + buf.extend(repeat(' ').take(right)); + print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size); + + if border.has_right() { + print_char(&mut buf, border.right.unwrap_or(' '), border_color.right); + } + + buf.push('\n'); + + line_index += 1; + } + } else { + let text_width = string_width_multiline(text); + let (left, _) = indent_horizontal(halignment, width, text_width); + + for line in get_lines(text) { + let line_width = string_width(&line); + let right = width - line_width - left; + + if border.has_left() { + let mut c = border.left.unwrap_or(' '); + if vertical_splits.first() == Some(&line_index) { + c = right_intersection; + vertical_splits = &vertical_splits[1..]; + } + + print_char(&mut buf, c, border_color.left); + } + + print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size); + buf.extend(repeat(' ').take(left)); + buf.push_str(&line); + buf.extend(repeat(' ').take(right)); + print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size); + + if border.has_right() { + print_char(&mut buf, border.right.unwrap_or(' '), border_color.right); + } + + buf.push('\n'); + + line_index += 1; + } + } + + for _ in 0..pad.bottom.size { + let mut border = border; + if vertical_splits.first() == Some(&line_index) { + border.left = Some(right_intersection); + vertical_splits = &vertical_splits[1..]; + } + + print_line( + &mut buf, + border, + border_color, + pad_color.bottom, + pad.bottom.fill, + ctx.size.width, + ); + + line_index += 1; + } + + for _ in 0..bottom { + let mut border = border; + if vertical_splits.first() == Some(&line_index) { + border.left = Some(right_intersection); + vertical_splits = &vertical_splits[1..]; + } + + print_line(&mut buf, border, border_color, None, ' ', ctx.size.width); + line_index += 1; + } + + print_bottom_line(&mut buf, border, border_color, ctx.size.width); + + let _ = buf.remove(buf.len() - 1); + + buf + } + + fn print_chars(buf: &mut String, c: char, color: Option<StaticColor>, width: usize) { + match color { + Some(color) => { + buf.push_str(color.get_prefix()); + buf.extend(repeat(c).take(width)); + buf.push_str(color.get_suffix()); + } + None => buf.extend(repeat(c).take(width)), + } + } + + fn print_char(buf: &mut String, c: char, color: Option<StaticColor>) { + match color { + Some(color) => { + buf.push_str(color.get_prefix()); + buf.push(c); + buf.push_str(color.get_suffix()); + } + None => buf.push(c), + } + } + + fn print_line( + buf: &mut String, + border: Border<char>, + border_color: Border<StaticColor>, + color: Option<StaticColor>, + c: char, + width: usize, + ) { + if border.has_left() { + let c = border.left.unwrap_or(' '); + print_char(buf, c, border_color.left); + } + + print_chars(buf, c, color, width); + + if border.has_right() { + let c = border.right.unwrap_or(' '); + print_char(buf, c, border_color.right); + } + + buf.push('\n'); + } + + fn print_top_line( + buf: &mut String, + border: Border<char>, + color: Border<StaticColor>, + splits: &[usize], + split_char: char, + width: usize, + ) { + if !border.has_top() { + return; + } + + let mut used_color: Option<StaticColor> = None; + + if border.has_left() { + if let Some(color) = color.left_top_corner { + used_color = Some(color); + buf.push_str(color.get_prefix()); + } + + let c = border.left_top_corner.unwrap_or(' '); + buf.push(c); + } + + if let Some(color) = color.top { + match used_color { + Some(used) => { + if used != color { + buf.push_str(used.get_suffix()); + buf.push_str(color.get_prefix()); + } + } + None => { + buf.push_str(color.get_prefix()); + used_color = Some(color); + } + } + } + + let c = border.top.unwrap_or(' '); + if splits.is_empty() { + buf.extend(repeat(c).take(width)); + } else { + let mut splits = splits; + for i in 0..width { + if splits.first() == Some(&i) { + buf.push(split_char); + splits = &splits[1..]; + } else { + buf.push(c); + } + } + } + + if border.has_right() { + if let Some(color) = color.right_top_corner { + match used_color { + Some(used) => { + if used != color { + buf.push_str(used.get_suffix()); + buf.push_str(color.get_prefix()); + } + } + None => { + buf.push_str(color.get_prefix()); + used_color = Some(color); + } + } + } + + let c = border.right_top_corner.unwrap_or(' '); + buf.push(c); + } + + if let Some(used) = used_color { + buf.push_str(used.get_suffix()); + } + + buf.push('\n'); + } + + fn print_bottom_line( + buf: &mut String, + border: Border<char>, + color: Border<StaticColor>, + width: usize, + ) { + if !border.has_bottom() { + return; + } + + let mut used_color: Option<StaticColor> = None; + + if border.has_left() { + if let Some(color) = color.left_bottom_corner { + used_color = Some(color); + buf.push_str(color.get_prefix()); + } + + let c = border.left_bottom_corner.unwrap_or(' '); + buf.push(c); + } + + if let Some(color) = color.bottom { + match used_color { + Some(used) => { + if used != color { + buf.push_str(used.get_suffix()); + buf.push_str(color.get_prefix()); + } + } + None => { + buf.push_str(color.get_prefix()); + used_color = Some(color); + } + } + } + + let c = border.bottom.unwrap_or(' '); + buf.extend(repeat(c).take(width)); + + if border.has_right() { + if let Some(color) = color.right_bottom_corner { + match used_color { + Some(used) => { + if used != color { + buf.push_str(used.get_suffix()); + buf.push_str(color.get_prefix()); + } + } + None => { + buf.push_str(color.get_prefix()); + used_color = Some(color); + } + } + } + + let c = border.right_bottom_corner.unwrap_or(' '); + buf.push(c); + } + + if let Some(used) = used_color { + buf.push_str(used.get_suffix()); + } + + buf.push('\n'); + } + + fn create_border<T>(borders: Borders<T>) -> Border<T> { + Border { + top: borders.top, + bottom: borders.bottom, + left: borders.left, + right: borders.right, + left_top_corner: borders.top_left, + left_bottom_corner: borders.bottom_left, + right_top_corner: borders.top_right, + right_bottom_corner: borders.bottom_right, + } + } + + fn config_borders(borders: &mut Borders<char>, ctx: &PrintContext) { + // set top_left + { + if ctx.kv && ctx.kv_is_first { + borders.top_left = borders.top_intersection; + } + + if ctx.kv && !ctx.kv_is_first { + borders.top_left = borders.intersection; + } + + if ctx.kv && ctx.list && !ctx.list_is_first { + borders.top_left = borders.left_intersection; + } + + if ctx.is_first_col && !ctx.is_first_row { + borders.top_left = borders.left_intersection; + } + + if ctx.lean_top { + borders.top_left = borders.top_intersection; + } + + if ctx.top_left { + borders.top_left = borders.left_intersection; + } + + if ctx.top_intersection { + borders.top_left = borders.intersection; + } + } + + if ctx.is_last_col && !ctx.is_first_row { + borders.top_right = borders.right_intersection; + } + + if !ctx.is_first_col && ctx.is_last_row { + borders.bottom_left = borders.bottom_intersection; + } + + if !ctx.is_last_row || ctx.no_bottom { + cfg_no_bottom_borders(borders); + } + + if ctx.no_right { + cfg_no_right_borders(borders); + } + } + + struct ConfigCell(PrintContext); + + impl<R, D> TableOption<R, D, ColoredConfig> for ConfigCell { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + { + // we set a horizontal lines to borders to not complicate logic with cleaning it + + let mut borders = *cfg.get_borders(); + if let Some(line) = cfg.get_horizontal_line(0) { + borders.top = line.main; + borders.top_left = line.left; + borders.top_right = line.right; + } + + if let Some(line) = cfg.get_horizontal_line(1) { + borders.bottom = line.main; + borders.bottom_left = line.left; + borders.bottom_right = line.right; + } + + cfg.clear_theme(); + cfg.set_borders(borders); + } + + let mut ctx = self.0; + + let has_vertical = cfg.get_borders().has_left(); + if !ctx.intersections_horizontal.is_empty() && has_vertical { + let mut splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width); + squash_splits(&mut splits); + + let c = cfg.get_borders().bottom_intersection.unwrap_or(' '); + cfg_set_top_chars(cfg, &splits, c) + } + + let has_horizontal = cfg.get_borders().has_top(); + if !ctx.intersections_vertical.is_empty() && has_horizontal { + let mut splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height); + squash_splits(&mut splits); + + let c = cfg.get_borders().right_intersection.unwrap_or(' '); + cfg_set_left_chars(cfg, &splits, c) + } + + let mut borders = *cfg.get_borders(); + + // set top_left + { + if ctx.kv && ctx.kv_is_first { + borders.top_left = borders.top_intersection; + } + + if ctx.kv && !ctx.kv_is_first { + borders.top_left = borders.intersection; + } + + if ctx.kv && ctx.list && !ctx.list_is_first { + borders.top_left = borders.left_intersection; + } + + if ctx.is_first_col && !ctx.is_first_row { + borders.top_left = borders.left_intersection; + } + + if ctx.lean_top { + borders.top_left = borders.top_intersection; + } + + if ctx.top_left { + borders.top_left = borders.left_intersection; + } + + if ctx.top_intersection { + borders.top_left = borders.intersection; + } + } + + if ctx.is_last_col && !ctx.is_first_row { + borders.top_right = borders.right_intersection; + } + + if !ctx.is_first_col && ctx.is_last_row { + borders.bottom_left = borders.bottom_intersection; + } + + if !ctx.is_last_row || ctx.no_bottom { + cfg_no_bottom_borders(&mut borders); + } + + if ctx.no_right { + cfg_no_right_borders(&mut borders); + } + + cfg.set_borders(borders); + } + } + + fn cfg_no_bottom_borders(borders: &mut Borders<char>) { + borders.bottom = None; + borders.bottom_intersection = None; + borders.bottom_left = None; + borders.bottom_right = None; + borders.horizontal = None; + } + + fn cfg_no_right_borders(borders: &mut Borders<char>) { + borders.right = None; + borders.right_intersection = None; + borders.top_right = None; + borders.bottom_right = None; + borders.vertical = None; + } + + fn cfg_set_top_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) { + for &split in list { + let offset = split; + cfg.set_horizontal_char((0, 0), c, Offset::Begin(offset)); + } + } + + fn cfg_set_left_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) { + for &offset in list { + cfg.set_vertical_char((0, 0), c, Offset::Begin(offset)); + } + } + + struct NoTopBorders; + + impl<R, D> TableOption<R, D, ColoredConfig> for NoTopBorders { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top = None; + borders.top_intersection = None; + borders.top_left = None; + borders.top_right = None; + + cfg.set_borders(borders); + } + } + + struct NoBottomBorders; + + impl<R, D> TableOption<R, D, ColoredConfig> for NoBottomBorders { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.bottom = None; + borders.bottom_intersection = None; + borders.bottom_left = None; + borders.bottom_right = None; + + cfg.set_borders(borders); + } + } + + struct NoRightBorders; + + impl<R, D> TableOption<R, D, ColoredConfig> for NoRightBorders { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top_right = None; + borders.bottom_right = None; + borders.right = None; + borders.right_intersection = None; + + cfg.set_borders(borders); + } + } + + struct NoLeftBorders; + + impl<R, D> TableOption<R, D, ColoredConfig> for NoLeftBorders { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top_left = None; + borders.bottom_left = None; + borders.left = None; + borders.left_intersection = None; + + cfg.set_borders(borders); + } + } + + struct TopLeftChangeTopIntersection; + + impl<R, D> TableOption<R, D, ColoredConfig> for TopLeftChangeTopIntersection { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top_left = borders.top_intersection; + + cfg.set_borders(borders); + } + } + + struct TopLeftChangeIntersection; + + impl<R, D> TableOption<R, D, ColoredConfig> for TopLeftChangeIntersection { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top_left = borders.intersection; + + cfg.set_borders(borders); + } + } + + struct TopLeftChangeToLeft; + + impl<R, D> TableOption<R, D, ColoredConfig> for TopLeftChangeToLeft { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top_left = borders.left_intersection; + + cfg.set_borders(borders); + } + } + + struct TopRightChangeToRight; + + impl<R, D> TableOption<R, D, ColoredConfig> for TopRightChangeToRight { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.top_right = borders.right_intersection; + + cfg.set_borders(borders); + } + } + + struct BottomLeftChangeSplit; + + impl<R, D> TableOption<R, D, ColoredConfig> for BottomLeftChangeSplit { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.bottom_left = borders.left_intersection; + + cfg.set_borders(borders); + } + } + + struct BottomLeftChangeSplitToIntersection; + + impl<R, D> TableOption<R, D, ColoredConfig> for BottomLeftChangeSplitToIntersection { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.bottom_left = borders.intersection; + + cfg.set_borders(borders); + } + } + + struct BottomRightChangeToRight; + + impl<R, D> TableOption<R, D, ColoredConfig> for BottomRightChangeToRight { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.bottom_right = borders.right_intersection; + + cfg.set_borders(borders); + } + } + + struct BottomLeftChangeToBottomIntersection; + + impl<R, D> TableOption<R, D, ColoredConfig> for BottomLeftChangeToBottomIntersection { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + let mut borders = *cfg.get_borders(); + borders.bottom_left = borders.bottom_intersection; + + cfg.set_borders(borders); + } + } + + struct SetBottomChars<'a>(&'a [usize], char); + + impl<R, D> TableOption<R, D, ColoredConfig> for SetBottomChars<'_> + where + R: Records, + for<'a> &'a R: Records, + for<'a> D: Dimension + Estimate<&'a R, ColoredConfig>, + { + fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { + dims.estimate(&*records, cfg); + + let table_width = (0..records.count_columns()) + .map(|col| dims.get_width(col)) + .sum::<usize>() + + cfg.count_vertical(records.count_columns()); + let mut current_width = 0; + + for pos in self.0 { + current_width += pos; + if current_width > table_width { + break; + } + + let split_char = self.1; + cfg.set_horizontal_char((1, 0), split_char, Offset::Begin(current_width)); + + current_width += 1; + } + } + } + + struct SetTopChars<'a>(&'a [usize], char); + + impl<R, D> TableOption<R, D, ColoredConfig> for SetTopChars<'_> { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + for &split in self.0 { + let offset = split; + cfg.set_horizontal_char((0, 0), self.1, Offset::Begin(offset)); + } + } + } + + struct SetLeftChars<'a>(&'a [usize], char); + + impl<R, D> TableOption<R, D, ColoredConfig> for SetLeftChars<'_> { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + for &offset in self.0 { + cfg.set_vertical_char((0, 0), self.1, Offset::Begin(offset)); + } + } + } + + struct GetTopIntersection(char); + + impl<R, D> TableOption<R, D, ColoredConfig> for &mut GetTopIntersection { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + self.0 = cfg.get_borders().top_intersection.unwrap_or(' '); + } + } + + struct GetBottomIntersection(char); + + impl<R, D> TableOption<R, D, ColoredConfig> for &mut GetBottomIntersection { + fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { + self.0 = cfg.get_borders().bottom_intersection.unwrap_or(' '); + } + } + + #[derive(Debug, Default)] + struct Dimensions { + all: HashMap<usize, Dim>, + arrays: HashMap<usize, ArrayDimensions>, + } + + #[derive(Debug, Default, Clone, Copy)] + struct Dim { + width: usize, + height: usize, + } + + impl Dim { + fn new(width: usize, height: usize) -> Self { + Self { width, height } + } + } + + #[derive(Debug, Default)] + struct ArrayDimensions { + max: Dim, + index: HashMap<usize, usize>, + } + + fn collect_table_dimensions(val: &TableValue, cfg: &CompactMultilineConfig) -> Dimensions { + let mut buf = Dimensions::default(); + let (dim, _) = __collect_table_dims(&mut buf, val, cfg, 0); + let _ = buf.all.insert(0, dim); + buf + } + + fn __collect_table_dims( + buf: &mut Dimensions, + val: &TableValue, + cfg: &CompactMultilineConfig, + pos: usize, + ) -> (Dim, usize) { + match val { + TableValue::Cell(text) => (str_dimension(text, cfg), 0), + TableValue::Row(list) => { + if list.is_empty() { + return (empty_dimension(cfg), 0); + } + + let mut index = ArrayDimensions { + max: Dim::default(), + index: HashMap::with_capacity(list.len()), + }; + + let mut total_width = 0; + + let mut count_elements = list.len(); + let mut val_pos = pos + 1; + for (i, value) in list.iter().enumerate() { + let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos); + count_elements += elements; + + total_width += dim.width; + + index.max.width = max(index.max.width, dim.width); + index.max.height = max(index.max.height, dim.height); + + let _ = buf.all.insert(val_pos, dim); + + let _ = index.index.insert(i, val_pos); + + val_pos += 1 + elements; + } + + let max_height = index.max.height; + + let _ = buf.arrays.insert(pos, index); + + let has_vertical = cfg.get_borders().has_left(); + total_width += has_vertical as usize * (list.len() - 1); + + (Dim::new(total_width, max_height), count_elements) + } + TableValue::Column(list) => { + if list.is_empty() { + return (empty_dimension(cfg), 0); + } + + let mut index = ArrayDimensions { + max: Dim::default(), + index: HashMap::with_capacity(list.len()), + }; + + let mut total_height = 0; + + let mut count_elements = list.len(); + let mut val_pos = pos + 1; + for (i, value) in list.iter().enumerate() { + let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos); + count_elements += elements; + + total_height += dim.height; + + index.max.width = max(index.max.width, dim.width); + index.max.height = max(index.max.height, dim.height); + + let _ = buf.all.insert(val_pos, dim); + + let _ = index.index.insert(i, val_pos); + + val_pos += 1 + elements; + } + + let max_width = index.max.width; + + let _ = buf.arrays.insert(pos, index); + + let has_horizontal = cfg.get_borders().has_top(); + total_height += has_horizontal as usize * (list.len() - 1); + + (Dim::new(max_width, total_height), count_elements) + } + } + } + + fn empty_dimension(cfg: &CompactMultilineConfig) -> Dim { + Dim::new(get_padding_horizontal(cfg), 1 + get_padding_vertical(cfg)) + } + + fn str_dimension(text: &str, cfg: &CompactMultilineConfig) -> Dim { + let (count_lines, width) = string_dimension(text); + let w = width + get_padding_horizontal(cfg); + let h = count_lines + get_padding_vertical(cfg); + Dim::new(w, h) + } + + fn get_padding_horizontal(cfg: &CompactMultilineConfig) -> usize { + let pad = cfg.get_padding(); + pad.left.size + pad.right.size + } + + fn get_padding_vertical(cfg: &CompactMultilineConfig) -> usize { + let pad = cfg.get_padding(); + pad.top.size + pad.bottom.size + } + + fn split_value(value: usize, by: usize) -> (usize, usize) { + let val = value / by; + let rest = value - val * by; + (val, rest) + } + + fn indent_vertical(al: AlignmentVertical, available: usize, real: usize) -> (usize, usize) { + let top = indent_top(al, available, real); + let bottom = available - real - top; + (top, bottom) + } + + fn indent_horizontal(al: AlignmentHorizontal, available: usize, real: usize) -> (usize, usize) { + let top = indent_left(al, available, real); + let right = available - real - top; + (top, right) + } + + fn indent_top(al: AlignmentVertical, available: usize, real: usize) -> usize { + match al { + AlignmentVertical::Top => 0, + AlignmentVertical::Bottom => available - real, + AlignmentVertical::Center => (available - real) / 2, + } + } + + fn indent_left(al: AlignmentHorizontal, available: usize, real: usize) -> usize { + match al { + AlignmentHorizontal::Left => 0, + AlignmentHorizontal::Right => available - real, + AlignmentHorizontal::Center => (available - real) / 2, + } + } + + fn short_splits(splits: &mut Vec<usize>, width: usize) -> Vec<usize> { + if splits.is_empty() { + return Vec::new(); + } + + let mut out = Vec::new(); + let mut pos = 0; + for &split in splits.iter() { + if pos + split >= width { + break; + } + + pos += split; + out.push(pos); + } + + let _ = splits.drain(..out.len()); + + if !splits.is_empty() && pos <= width { + let rest = width - pos; + splits[0] -= rest; + } + + out + } + + fn short_splits3(splits: &mut Vec<usize>, width: usize) -> (bool, Vec<usize>) { + if splits.is_empty() { + return (false, Vec::new()); + } + + let mut out = Vec::new(); + let mut pos = 0; + for &split in splits.iter() { + if pos + split >= width { + break; + } + + pos += split + 1; + out.push(split); + } + + let _ = splits.drain(..out.len()); + + if splits.is_empty() { + return (false, out); + } + + if pos <= width { + splits[0] -= width - pos; + if splits[0] > 0 { + splits[0] -= 1; + } else { + let _ = splits.remove(0); + return (true, out); + } + } + + (false, out) + } + + fn squash_splits(splits: &mut [usize]) { + splits.iter_mut().enumerate().for_each(|(i, s)| *s += i); + } + + fn set_margin(table: &str, margin: Sides<Indent>, color: Sides<Option<StaticColor>>) -> String { + if table.is_empty() { + return String::new(); + } + + let mut buf = String::new(); + let width = string_width_multiline(table); + let top_color = color.top; + let bottom_color = color.bottom; + let left_color = color.left; + let right_color = color.right; + for _ in 0..margin.top.size { + print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); + print_chars(&mut buf, margin.top.fill, top_color, width); + print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); + buf.push('\n'); + } + + for line in get_lines(table) { + print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); + buf.push_str(&line); + print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); + buf.push('\n'); + } + + for _ in 0..margin.bottom.size { + print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); + print_chars(&mut buf, margin.bottom.fill, bottom_color, width); + print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); + buf.push('\n'); + } + + let _ = buf.remove(buf.len() - 1); + + buf + } + + fn convert_border_colors(pad_color: Sides<StaticColor>) -> Sides<Option<StaticColor>> { + Sides::new( + (!pad_color.left.is_empty()).then(|| pad_color.left), + (!pad_color.right.is_empty()).then(|| pad_color.right), + (!pad_color.top.is_empty()).then(|| pad_color.top), + (!pad_color.bottom.is_empty()).then(|| pad_color.bottom), + ) + } +} diff --git a/vendor/tabled/src/tables/util/mod.rs b/vendor/tabled/src/tables/util/mod.rs new file mode 100644 index 000000000..ea5453b8f --- /dev/null +++ b/vendor/tabled/src/tables/util/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "std")] +pub(crate) mod utf8_writer; diff --git a/vendor/tabled/src/tables/util/utf8_writer.rs b/vendor/tabled/src/tables/util/utf8_writer.rs new file mode 100644 index 000000000..d4fc515df --- /dev/null +++ b/vendor/tabled/src/tables/util/utf8_writer.rs @@ -0,0 +1,29 @@ +use std::fmt; +use std::io; + +pub(crate) struct UTF8Writer<W>(W); + +impl<W> UTF8Writer<W> { + pub(crate) fn new(writer: W) -> Self { + Self(writer) + } +} + +impl<W> fmt::Write for UTF8Writer<W> +where + W: io::Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + let mut buf = s.as_bytes(); + loop { + let n = self.0.write(buf).map_err(|_| fmt::Error)?; + if n == buf.len() { + break; + } + + buf = &buf[n..]; + } + + Ok(()) + } +} diff --git a/vendor/tabled/tests/core/builder_test.rs b/vendor/tabled/tests/core/builder_test.rs new file mode 100644 index 000000000..7751df12d --- /dev/null +++ b/vendor/tabled/tests/core/builder_test.rs @@ -0,0 +1,748 @@ +#![cfg(feature = "std")] + +use std::iter::FromIterator; + +use tabled::builder::Builder; + +use testing_table::test_table; + +test_table!( + push_record, + { + let mut b = Builder::default(); + b.push_record(["1", "2", "3"]); + b.push_record(["a", "b", "c"]); + b.push_record(["d", "e", "f"]); + b.build() + }, + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + set_header, + { + let mut b = Builder::default(); + b.set_header(["1", "2", "3"]); + b.push_record(["a", "b", "c"]); + b.push_record(["d", "e", "f"]); + b.build() + }, + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + header_remove_0, + { + let mut b = Builder::default(); + b.set_header(["1", "2", "3"]); + b.push_record(["a", "b", "c"]); + b.push_record(["d", "e", "f"]); + b.remove_header(); + b.build() + }, + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + header_remove_1, + { + let mut b = Builder::default(); + b.set_header(["1", "2", "3", "4", "5"]); + b.push_record(["a", "b", "c"]); + b.push_record(["d", "e", "f"]); + b.remove_header(); + b.build() + }, + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + from_iter, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]).build(), + "+---+-----------+" + "| n | name |" + "+---+-----------+" + "| 0 | Dmitriy |" + "+---+-----------+" + "| 1 | Vladislav |" + "+---+-----------+" +); + +test_table!( + used_with_different_number_of_columns_0, + { + let mut b = Builder::default(); + b.set_header(["1", "2"]); + b.push_record(["a", "b", "c"]); + b.push_record(["d"]); + b.build() + }, + "+---+---+---+" + "| 1 | 2 | |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | | |" + "+---+---+---+" +); + +test_table!( + used_with_different_number_of_columns_1, + { + let mut b = Builder::default(); + b.set_header(["1", "2", "3"]); + b.push_record(["a", "b"]); + b.push_record(["d"]); + b.build() + }, + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | |" + "+---+---+---+" + "| d | | |" + "+---+---+---+" +); + +test_table!( + used_with_different_number_of_columns_2, + { + let mut b = Builder::default(); + b.set_header(["1"]); + b.push_record(["a", "b"]); + b.push_record(["d", "e", "f"]); + b.build() + }, + "+---+---+---+" + "| 1 | | |" + "+---+---+---+" + "| a | b | |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + with_default_cell_0, + { + let mut b = Builder::default(); + b.set_header(["1", "2"]).set_default_text("NaN").push_record(["a", "b", "c"]).push_record(["d"]); + b.build() + }, + "+---+-----+-----+" + "| 1 | 2 | NaN |" + "+---+-----+-----+" + "| a | b | c |" + "+---+-----+-----+" + "| d | NaN | NaN |" + "+---+-----+-----+" +); + +test_table!( + with_default_cell_1, + { + let mut b = Builder::default(); + b.set_header(["1"]).set_default_text("NaN").push_record(["a", "b"]).push_record(["d", "e", "f"]); + b.build() + }, + "+---+-----+-----+" + "| 1 | NaN | NaN |" + "+---+-----+-----+" + "| a | b | NaN |" + "+---+-----+-----+" + "| d | e | f |" + "+---+-----+-----+" +); + +test_table!( + extend, + { + let mut b = Builder::default(); + b.extend(["1", "2", "3"]); + b.extend(["a", "b", "c"]); + b.extend(["d", "e", "f"]); + b.build() + }, + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + from_vector_0, + Builder::from_iter(vec![ + vec!["1".to_string(), "2".to_string(), "3".to_string()], + vec!["a".to_string(), "b".to_string(), "c".to_string()], + vec!["d".to_string(), "e".to_string(), "f".to_string()], + ]) + .build(), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + from_with_empty_lines_0, + Builder::from_iter(vec![ + vec!["1".to_string(), "2".to_string(), "3".to_string()], + vec![], + vec![], + vec!["d".to_string(), "e".to_string(), "f".to_string()], + ]) + .build(), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| | | |" + "+---+---+---+" + "| | | |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + from_with_empty_lines_1, + Builder::from_iter(vec![ + vec!["1".to_string(), "2".to_string(), "3".to_string()], + vec![], + vec!["d".to_string(), "e".to_string(), "f".to_string()], + ]) + .build(), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| | | |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + from_with_empty_lines_2, + Builder::from_iter(vec![ + vec![], + vec!["1".to_string(), "2".to_string(), "3".to_string()], + vec!["d".to_string(), "e".to_string(), "f".to_string()], + ]) + .build(), + "+---+---+---+" + "| | | |" + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + from_with_empty_lines_3, + Builder::from_iter(vec![ + vec!["1".to_string(), "2".to_string(), "3".to_string()], + vec!["d".to_string(), "e".to_string(), "f".to_string()], + vec![], + ]) + .build(), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" + "| | | |" + "+---+---+---+" +); + +test_table!( + clean_0, + clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]])), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + clean_1, + clean(Builder::from_iter([["", "2", "3"], ["", "b", "c"], ["", "e", "f"]])), + "+---+---+" + "| 2 | 3 |" + "+---+---+" + "| b | c |" + "+---+---+" + "| e | f |" + "+---+---+" +); + +test_table!( + clean_2, + clean(Builder::from_iter([["1", "", "3"], ["a", "", "c"], ["d", "", "f"]])), + "+---+---+" + "| 1 | 3 |" + "+---+---+" + "| a | c |" + "+---+---+" + "| d | f |" + "+---+---+" +); + +test_table!( + clean_3, + clean(Builder::from_iter([["1", "2", ""], ["a", "b", ""], ["d", "e", ""]])), + "+---+---+" + "| 1 | 2 |" + "+---+---+" + "| a | b |" + "+---+---+" + "| d | e |" + "+---+---+" +); + +test_table!( + clean_4, + clean(Builder::from_iter([["", "", "3"], ["", "", "c"], ["", "", "f"]])), + "+---+" + "| 3 |" + "+---+" + "| c |" + "+---+" + "| f |" + "+---+" +); + +test_table!( + clean_5, + clean(Builder::from_iter([["1", "", ""], ["a", "", ""], ["d", "", ""]])), + "+---+" + "| 1 |" + "+---+" + "| a |" + "+---+" + "| d |" + "+---+" +); + +test_table!( + clean_6, + clean(Builder::from_iter([["", "2", ""], ["", "b", ""], ["", "e", ""]])), + "+---+" + "| 2 |" + "+---+" + "| b |" + "+---+" + "| e |" + "+---+" +); + +test_table!( + clean_7, + clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["d", "e", "f"]])), + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + clean_8, + clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["d", "e", "f"]])), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + clean_9, + clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["", "", ""]])), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" +); + +test_table!( + clean_10, + clean(Builder::from_iter([["", "", ""], ["", "", ""], ["d", "e", "f"]])), + "+---+---+---+" + "| d | e | f |" + "+---+---+---+" +); + +test_table!( + clean_11, + clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["", "", ""]])), + "+---+---+---+" + "| 1 | 2 | 3 |" + "+---+---+---+" +); + +test_table!( + clean_12, + clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["", "", ""]])), + "+---+---+---+" + "| a | b | c |" + "+---+---+---+" +); + +test_table!( + clean_13, + clean(Builder::from_iter([["1", "", "3"], ["", "", ""], ["d", "", "f"]])), + "+---+---+" + "| 1 | 3 |" + "+---+---+" + "| d | f |" + "+---+---+" +); + +test_table!( + clean_with_columns_0, + clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| 1 | 2 | 3 |" + "+------+------+------+" + "| a | b | c |" + "+------+------+------+" + "| d | e | f |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_1, + clean(Builder::from_iter([["", "2", "3"], ["", "b", "c"], ["", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+" + "| col2 | col3 |" + "+------+------+" + "| 2 | 3 |" + "+------+------+" + "| b | c |" + "+------+------+" + "| e | f |" + "+------+------+" +); + +test_table!( + clean_with_columns_2, + clean(Builder::from_iter([["1", "", "3"], ["a", "", "c"], ["d", "", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+" + "| col1 | col3 |" + "+------+------+" + "| 1 | 3 |" + "+------+------+" + "| a | c |" + "+------+------+" + "| d | f |" + "+------+------+" +); + +test_table!( + clean_with_columns_3, + clean(Builder::from_iter([["1", "2", ""], ["a", "b", ""], ["d", "e", ""]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+" + "| col1 | col2 |" + "+------+------+" + "| 1 | 2 |" + "+------+------+" + "| a | b |" + "+------+------+" + "| d | e |" + "+------+------+" +); + +test_table!( + clean_with_columns_4, + clean(Builder::from_iter([["", "", "3"], ["", "", "c"], ["", "", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+" + "| col3 |" + "+------+" + "| 3 |" + "+------+" + "| c |" + "+------+" + "| f |" + "+------+" +); + +test_table!( + clean_with_columns_5, + clean(Builder::from_iter([["1", "", ""], ["a", "", ""], ["d", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+" + "| col1 |" + "+------+" + "| 1 |" + "+------+" + "| a |" + "+------+" + "| d |" + "+------+" +); + +test_table!( + clean_with_columns_6, + clean(Builder::from_iter([["", "2", ""], ["", "b", ""], ["", "e", ""]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+" + "| col2 |" + "+------+" + "| 2 |" + "+------+" + "| b |" + "+------+" + "| e |" + "+------+" +); + +test_table!( + clean_with_columns_7, + clean( + Builder::from_iter([["", "", ""], ["", "", ""], ["", "", ""]]) + .set_header(["col1", "col2", "col3"]) + .clone() + ), + "" +); + +test_table!( + clean_with_columns_8, + clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| a | b | c |" + "+------+------+------+" + "| d | e | f |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_9, + clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| 1 | 2 | 3 |" + "+------+------+------+" + "| d | e | f |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_10, + clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| 1 | 2 | 3 |" + "+------+------+------+" + "| a | b | c |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_11, + clean(Builder::from_iter([["", "", ""], ["", "", ""], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| d | e | f |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_12, + clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| 1 | 2 | 3 |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_13, + clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+------+" + "| col1 | col2 | col3 |" + "+------+------+------+" + "| a | b | c |" + "+------+------+------+" +); + +test_table!( + clean_with_columns_14, + clean(Builder::from_iter([["1", "", "3"], ["", "", ""], ["d", "", "f"]]).set_header(["col1", "col2", "col3"]).clone()), + "+------+------+" + "| col1 | col3 |" + "+------+------+" + "| 1 | 3 |" + "+------+------+" + "| d | f |" + "+------+------+" +); + +test_table!(clean_empty_0, clean(Builder::from_iter([[""; 0]; 0])), ""); + +test_table!(clean_empty_1, clean(Builder::from_iter([[""; 0]; 10])), ""); + +test_table!( + index, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]).index().build(), + "+---+---+-----------+" + "| | 0 | 1 |" + "+---+---+-----------+" + "| 0 | n | name |" + "+---+---+-----------+" + "| 1 | 0 | Dmitriy |" + "+---+---+-----------+" + "| 2 | 1 | Vladislav |" + "+---+---+-----------+" +); + +test_table!( + index_set_name, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) + .index() + .name(Some("A index name".into())) + .build(), + "+--------------+---+-----------+" + "| | 0 | 1 |" + "+--------------+---+-----------+" + "| A index name | | |" + "+--------------+---+-----------+" + "| 0 | n | name |" + "+--------------+---+-----------+" + "| 1 | 0 | Dmitriy |" + "+--------------+---+-----------+" + "| 2 | 1 | Vladislav |" + "+--------------+---+-----------+" +); + +test_table!( + index_enumeration, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) + .index() + .hide() + .build(), + "+---+-----------+" + "| 0 | 1 |" + "+---+-----------+" + "| n | name |" + "+---+-----------+" + "| 0 | Dmitriy |" + "+---+-----------+" + "| 1 | Vladislav |" + "+---+-----------+" +); + +test_table!( + set_index, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) + .index() + .column(1) + .build(), + "+-----------+---+" + "| | 0 |" + "+-----------+---+" + "| 1 | |" + "+-----------+---+" + "| name | n |" + "+-----------+---+" + "| Dmitriy | 0 |" + "+-----------+---+" + "| Vladislav | 1 |" + "+-----------+---+" +); + +test_table!( + set_index_and_set_index_name_0, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) + .index() + .column(1) + .name(Some("Hello".into())) + .build(), + "+-----------+---+" + "| | 0 |" + "+-----------+---+" + "| Hello | |" + "+-----------+---+" + "| name | n |" + "+-----------+---+" + "| Dmitriy | 0 |" + "+-----------+---+" + "| Vladislav | 1 |" + "+-----------+---+" +); + +test_table!( + set_index_and_set_index_name_1, + Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) + .index() + .column(1) + .name(None) + .build(), + "+-----------+---+" + "| | 0 |" + "+-----------+---+" + "| name | n |" + "+-----------+---+" + "| Dmitriy | 0 |" + "+-----------+---+" + "| Vladislav | 1 |" + "+-----------+---+" +); + +test_table!( + index_transpose, + Builder::from_iter([["n", "name", "zz"], ["0", "Dmitriy", "123"], ["1", "Vladislav", "123"]]) + .index() + .transpose() + .build(), + "+---+------+---------+-----------+" + "| | 0 | 1 | 2 |" + "+---+------+---------+-----------+" + "| 0 | n | 0 | 1 |" + "+---+------+---------+-----------+" + "| 1 | name | Dmitriy | Vladislav |" + "+---+------+---------+-----------+" + "| 2 | zz | 123 | 123 |" + "+---+------+---------+-----------+" +); + +fn clean(mut b: Builder) -> String { + b.clean(); + b.build().to_string() +} diff --git a/vendor/tabled/tests/core/compact_table.rs b/vendor/tabled/tests/core/compact_table.rs new file mode 100644 index 000000000..1bdaad998 --- /dev/null +++ b/vendor/tabled/tests/core/compact_table.rs @@ -0,0 +1,119 @@ +#![cfg(feature = "std")] + +use tabled::{ + grid::{ + config::CompactConfig, dimension::CompactGridDimension, dimension::Estimate, + records::IterRecords, + }, + tables::CompactTable, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + compact_new, + CompactTable::new(Matrix::new(3, 3).to_vec()).to_string(), + "" +); + +test_table!( + compact_with_dimension, + { + let data = Matrix::with_no_frame(3, 3).to_vec(); + let mut dims = CompactGridDimension::default(); + dims.estimate(IterRecords::new(&data, 3, None), &CompactConfig::default()); + CompactTable::with_dimension(data, dims).columns(3).to_string() + }, + "+-----+-----+-----+" + "| 0-0 | 0-1 | 0-2 |" + "|-----+-----+-----|" + "| 1-0 | 1-1 | 1-2 |" + "|-----+-----+-----|" + "| 2-0 | 2-1 | 2-2 |" + "+-----+-----+-----+" +); + +test_table!( + compact_width, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec().to_vec()).columns(3).width(5).to_string(), + "+-----+-----+-----+" + "| 0-0 | 0-1 | 0-2 |" + "|-----+-----+-----|" + "| 1-0 | 1-1 | 1-2 |" + "|-----+-----+-----|" + "| 2-0 | 2-1 | 2-2 |" + "+-----+-----+-----+" +); + +test_table!( + compact_width_pad_not_included, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3).width(3).to_string(), + "+---+---+---+" + "| 0-0 | 0-1 | 0-2 |" + "|---+---+---|" + "| 1-0 | 1-1 | 1-2 |" + "|---+---+---|" + "| 2-0 | 2-1 | 2-2 |" + "+---+---+---+" +); + +test_table!( + compact_width_bigger, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3).width(10).to_string(), + "+----------+----------+----------+" + "| 0-0 | 0-1 | 0-2 |" + "|----------+----------+----------|" + "| 1-0 | 1-1 | 1-2 |" + "|----------+----------+----------|" + "| 2-0 | 2-1 | 2-2 |" + "+----------+----------+----------+" +); + +test_table!( + compact_columns, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3).to_string(), + "+--+--+--+" + "| 0-0 | 0-1 | 0-2 |" + "|--+--+--|" + "| 1-0 | 1-1 | 1-2 |" + "|--+--+--|" + "| 2-0 | 2-1 | 2-2 |" + "+--+--+--+" +); + +test_table!( + compact_cols_zero, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()) + .columns(0) + .to_string(), + "" +); + +test_table!( + compact_cols_less, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()) + .columns(1) + .to_string(), + "+--+" + "| 0-0 |" + "|--|" + "| 1-0 |" + "|--|" + "| 2-0 |" + "+--+" +); + +test_table!( + compact_cols_more, + CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()) + .columns(5) + .to_string(), + "+--+--+--+--+--+" + "| 0-0 | 0-1 | 0-2 |" + "|--+--+--+--+--|" + "| 1-0 | 1-1 | 1-2 |" + "|--+--+--+--+--|" + "| 2-0 | 2-1 | 2-2 |" + "+--+--+--+--+--+" +); diff --git a/vendor/tabled/tests/core/extended_table_test.rs b/vendor/tabled/tests/core/extended_table_test.rs new file mode 100644 index 000000000..8e511127d --- /dev/null +++ b/vendor/tabled/tests/core/extended_table_test.rs @@ -0,0 +1,472 @@ +#![cfg(feature = "std")] + +#[cfg(feature = "color")] +use owo_colors::{AnsiColors, OwoColorize}; + +use tabled::{tables::ExtendedTable, Tabled}; + +use crate::matrix::Matrix; +use testing_table::{static_table, test_table}; + +macro_rules! assert_expanded_display { + ( $data:expr, $expected:expr ) => { + let table = ExtendedTable::new($data).to_string(); + assert_eq!(table, $expected); + }; +} + +macro_rules! build_tabled_type { + ( $name:ident, $length:expr, $fields:expr, $headers:expr ) => { + #[derive(Debug, Clone, Copy)] + struct $name; + + impl Tabled for $name { + const LENGTH: usize = $length; + + fn fields(&self) -> Vec<std::borrow::Cow<'_, str>> { + $fields.iter().map(|s| s.to_string().into()).collect() + } + + fn headers() -> Vec<std::borrow::Cow<'static, str>> { + $headers.iter().map(|s| s.to_string().into()).collect() + } + } + }; +} + +test_table!( + display, + ExtendedTable::from(Matrix::vec(3, 3)), + "-[ RECORD 0 ]-" + "N | 0" + "column 0 | 0-0" + "column 1 | 0-1" + "column 2 | 0-2" + "-[ RECORD 1 ]-" + "N | 1" + "column 0 | 1-0" + "column 1 | 1-1" + "column 2 | 1-2" + "-[ RECORD 2 ]-" + "N | 2" + "column 0 | 2-0" + "column 1 | 2-1" + "column 2 | 2-2" +); + +#[test] +fn display_empty_records() { + build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["1", "2", "3"]); + let data: Vec<TestType> = vec![]; + assert_expanded_display!(data, ""); +} + +#[test] +fn display_empty() { + build_tabled_type!( + TestType, + 3, + { + let d: Vec<String> = vec![]; + d + }, + { + let d: Vec<String> = vec![]; + d + } + ); + let data: Vec<TestType> = vec![]; + assert_expanded_display!(data, ""); +} + +#[test] +fn display_empty_2() { + build_tabled_type!(EmptyType, 0, [""; 0], [""; 0]); + assert_expanded_display!(&[EmptyType], "-[ RECORD 0 ]-"); +} + +#[test] +fn display_dynamic_header_template() { + { + build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["1", "2", "3"]); + assert_expanded_display!( + &[TestType], + static_table!( + "-[ RECORD 0 ]-" + "1 | He" + "2 | 123" + "3 | asd" + ) + ); + } + { + build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["11", "2222222", "3"]); + assert_expanded_display!( + &[TestType], + static_table!( + "-[ RECORD 0 ]-" + "11 | He" + "2222222 | 123" + "3 | asd" + ) + ); + } + { + build_tabled_type!( + TestType, + 3, + ["HeheHehe", "123", "asd"], + ["11", "2222222", "3"] + ); + assert_expanded_display!( + &[TestType], + static_table!( + "-[ RECORD 0 ]-----" + "11 | HeheHehe" + "2222222 | 123" + "3 | asd" + ) + ); + } + { + build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["11111111111", "2", "3"]); + assert_expanded_display!( + &[TestType], + static_table!( + "-[ RECORD 0 ]----" + "11111111111 | He" + "2 | 123" + "3 | asd" + ) + ); + } + { + build_tabled_type!( + TestType, + 3, + ["He", "123", "asd"], + ["1111111111111", "2", "3"] + ); + assert_expanded_display!( + &[TestType], + static_table!( + "-[ RECORD 0 ]-+----" + "1111111111111 | He" + "2 | 123" + "3 | asd" + ) + ); + } + { + build_tabled_type!( + TestType, + 3, + ["He", "123", "asd"], + ["11111111111111111111111111111", "2", "3"] + ); + assert_expanded_display!( + &[TestType], + static_table!( + "-[ RECORD 0 ]-----------------+----" + "11111111111111111111111111111 | He" + "2 | 123" + "3 | asd" + ) + ); + } + { + build_tabled_type!(TestType, 3, ["22"], ["11111111111"]); + assert_expanded_display!( + std::iter::repeat(TestType).take(11), + static_table!( + "-[ RECORD 0 ]---" + "11111111111 | 22" + "-[ RECORD 1 ]---" + "11111111111 | 22" + "-[ RECORD 2 ]---" + "11111111111 | 22" + "-[ RECORD 3 ]---" + "11111111111 | 22" + "-[ RECORD 4 ]---" + "11111111111 | 22" + "-[ RECORD 5 ]---" + "11111111111 | 22" + "-[ RECORD 6 ]---" + "11111111111 | 22" + "-[ RECORD 7 ]---" + "11111111111 | 22" + "-[ RECORD 8 ]---" + "11111111111 | 22" + "-[ RECORD 9 ]---" + "11111111111 | 22" + "-[ RECORD 10 ]--" + "11111111111 | 22" + ) + ); + } +} + +#[test] +fn display_multiline_field() { + build_tabled_type!(TestType, 3, ["1", "2", "3"], ["Hello\nWorld", "123", "asd"]); + assert_expanded_display!( + [TestType], + static_table!( + "-[ RECORD 0 ]---" + "Hello\\nWorld | 1" + "123 | 2" + "asd | 3" + ) + ); +} + +#[test] +fn display_multiline_record_value() { + let mut data = Matrix::list::<2, 3>(); + data[0][0] = "Hello\nWorld".to_string(); + data[0][1] = "123".to_string(); + data[0][2] = "asd".to_string(); + + assert_expanded_display!( + data, + static_table!( + "-[ RECORD 0 ]----------" + "N | Hello\\nWorld" + "column 0 | 123" + "column 1 | asd" + "column 2 | 0-2" + "-[ RECORD 1 ]----------" + "N | 1" + "column 0 | 1-0" + "column 1 | 1-1" + "column 2 | 1-2" + ) + ); +} + +test_table!( + display_with_truncate, + { + let data = Matrix::new(3, 3).insert((1, 0), "a long string").to_vec(); + let mut table = ExtendedTable::from(data); + table.truncate(14, ""); + table.to_string() + }, + "-[ RECORD 0 ]-" + "N | a l" + "column 0 | 0-0" + "column 1 | 0-1" + "column 2 | 0-2" + "-[ RECORD 1 ]-" + "N | 1" + "column 0 | 1-0" + "column 1 | 1-1" + "column 2 | 1-2" + "-[ RECORD 2 ]-" + "N | 2" + "column 0 | 2-0" + "column 1 | 2-1" + "column 2 | 2-2" +); + +test_table!( + truncate_with_suffix, + { + let data = Matrix::new(3, 3).insert((1, 0), "a long string").to_vec(); + let mut table = ExtendedTable::from(data); + table.truncate(15, ".."); + table.to_string() + }, + "-[ RECORD 0 ]-" + "N | .." + "column 0 | .." + "column 1 | .." + "column 2 | .." + "-[ RECORD 1 ]-" + "N | .." + "column 0 | .." + "column 1 | .." + "column 2 | .." + "-[ RECORD 2 ]-" + "N | .." + "column 0 | .." + "column 1 | .." + "column 2 | .." +); + +#[test] +fn truncate_big_fields() { + build_tabled_type!( + TestType, + 3, + ["1", "2", "3"], + ["A quite big field", "123", "asd"] + ); + let data: Vec<TestType> = vec![TestType, TestType]; + + let mut table = ExtendedTable::new(&data); + table.truncate(14, ".."); + let table = table.to_string(); + + assert_eq!( + table, + static_table!( + "-[ RECORD 0 ]-" + "A quite.. | .." + "123 | .." + "asd | .." + "-[ RECORD 1 ]-" + "A quite.. | .." + "123 | .." + "asd | .." + ) + ); + + let mut table = ExtendedTable::new(&data); + table.truncate(15, ".."); + let table = table.to_string(); + + assert_eq!( + table, + static_table!( + "-[ RECORD 0 ]--" + "A quite .. | .." + "123 | .." + "asd | .." + "-[ RECORD 1 ]--" + "A quite .. | .." + "123 | .." + "asd | .." + ) + ); + + let mut table = ExtendedTable::new(&data); + table.truncate(0, ".."); + let table = table.to_string(); + + assert_eq!( + table, + static_table!( + "-[ RECORD 0 ]-----+--" + "A quite big field | 1" + "123 | 2" + "asd | 3" + "-[ RECORD 1 ]-----+--" + "A quite big field | 1" + "123 | 2" + "asd | 3" + ) + ); + + let mut table = ExtendedTable::new(&data); + table.truncate(20, "......"); + let table = table.to_string(); + + assert_eq!( + table, + static_table!( + "-[ RECORD 0 ]-------" + "A qui...... | ......" + "123 | ......" + "asd | ......" + "-[ RECORD 1 ]-------" + "A qui...... | ......" + "123 | ......" + "asd | ......" + ) + ); +} + +test_table!( + truncate_too_small, + { + let data = Matrix::new(3, 3).insert((1, 0), "a long string").to_vec(); + let mut table = ExtendedTable::from(data); + let success = table.truncate(2, ""); + assert!(!success); + table + }, + "-[ RECORD 0 ]-----------" + "N | a long string" + "column 0 | 0-0" + "column 1 | 0-1" + "column 2 | 0-2" + "-[ RECORD 1 ]-----------" + "N | 1" + "column 0 | 1-0" + "column 1 | 1-1" + "column 2 | 1-2" + "-[ RECORD 2 ]-----------" + "N | 2" + "column 0 | 2-0" + "column 1 | 2-1" + "column 2 | 2-2" +); + +#[cfg(feature = "color")] +#[test] +fn display_colored() { + let mut data = Matrix::list::<3, 3>(); + data[0][2] = "https://getfedora.org/" + .red() + .on_color(AnsiColors::Blue) + .to_string(); + data[1][2] = "https://www.opensuse.org/" + .green() + .on_color(AnsiColors::Black) + .to_string(); + data[2][2] = "https://endeavouros.com/".blue().underline().to_string(); + + assert_expanded_display!( + data, + static_table!( + "-[ RECORD 0 ]------------------------------------------------------------" + "N | 0" + "column 0 | 0-0" + "column 1 | \\u{1b}[31;44mhttps://getfedora.org/\\u{1b}[0m" + "column 2 | 0-2" + "-[ RECORD 1 ]------------------------------------------------------------" + "N | 1" + "column 0 | 1-0" + "column 1 | \\u{1b}[32;40mhttps://www.opensuse.org/\\u{1b}[0m" + "column 2 | 1-2" + "-[ RECORD 2 ]------------------------------------------------------------" + "N | 2" + "column 0 | 2-0" + "column 1 | \\u{1b}[4m\\u{1b}[34mhttps://endeavouros.com/\\u{1b}[39m\\u{1b}[0m" + "column 2 | 2-2" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn display_with_truncate_colored() { + let mut data = Matrix::list::<2, 3>(); + data[0][2] = "https://getfedora.org/".red().to_string(); + data[1][1] = "https://endeavouros.com/" + .white() + .on_color(AnsiColors::Black) + .to_string(); + data[1][2] = "https://www.opensuse.org/".to_string(); + + let mut table = ExtendedTable::new(&data); + table.truncate(20, ""); + let table = table.to_string(); + + assert_eq!( + table, + static_table!( + "-[ RECORD 0 ]-------" + "N | 0" + "column 0 | 0-0" + "column 1 | \\u{1b}[31" + "column 2 | 0-2" + "-[ RECORD 1 ]-------" + "N | 1" + "column 0 | \\u{1b}[37" + "column 1 | https://w" + "column 2 | 1-2" + ) + ); +} diff --git a/vendor/tabled/tests/core/index_test.rs b/vendor/tabled/tests/core/index_test.rs new file mode 100644 index 000000000..f6e107a5a --- /dev/null +++ b/vendor/tabled/tests/core/index_test.rs @@ -0,0 +1,194 @@ +#![cfg(feature = "std")] + +use std::iter::FromIterator; + +use crate::matrix::Matrix; +use tabled::{builder::Builder, Table}; +use testing_table::test_table; + +test_table!( + builder_index, + Table::builder(Matrix::list::<3, 2>()).index().build(), + "+---+---+----------+----------+" + "| | N | column 0 | column 1 |" + "+---+---+----------+----------+" + "| 0 | 0 | 0-0 | 0-1 |" + "+---+---+----------+----------+" + "| 1 | 1 | 1-0 | 1-1 |" + "+---+---+----------+----------+" + "| 2 | 2 | 2-0 | 2-1 |" + "+---+---+----------+----------+" +); + +test_table!( + builder_index_transpose, + Table::builder(Matrix::list::<4, 2>()).index().transpose().build(), + "+----------+-----+-----+-----+-----+" + "| | 0 | 1 | 2 | 3 |" + "+----------+-----+-----+-----+-----+" + "| N | 0 | 1 | 2 | 3 |" + "+----------+-----+-----+-----+-----+" + "| column 0 | 0-0 | 1-0 | 2-0 | 3-0 |" + "+----------+-----+-----+-----+-----+" + "| column 1 | 0-1 | 1-1 | 2-1 | 3-1 |" + "+----------+-----+-----+-----+-----+" +); + +test_table!( + builder_index_0, + Table::builder(Matrix::list::<4, 2>()).index().column(0).build(), + "+---+----------+----------+" + "| | column 0 | column 1 |" + "+---+----------+----------+" + "| N | | |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" + "| 2 | 2-0 | 2-1 |" + "+---+----------+----------+" + "| 3 | 3-0 | 3-1 |" + "+---+----------+----------+" +); + +test_table!( + builder_index_0_no_name, + Table::builder(Matrix::list::<4, 2>()).index().column(0).name(None).build(), + "+---+----------+----------+" + "| | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" + "| 2 | 2-0 | 2-1 |" + "+---+----------+----------+" + "| 3 | 3-0 | 3-1 |" + "+---+----------+----------+" +); + +test_table!( + builder_index_0_name, + Table::builder(Matrix::list::<4, 2>()).index().column(0).name(Some("Hello World".into())).build(), + "+-------------+----------+----------+" + "| | column 0 | column 1 |" + "+-------------+----------+----------+" + "| Hello World | | |" + "+-------------+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+-------------+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+-------------+----------+----------+" + "| 2 | 2-0 | 2-1 |" + "+-------------+----------+----------+" + "| 3 | 3-0 | 3-1 |" + "+-------------+----------+----------+" +); + +test_table!( + builder_index_0_name_transpose, + Table::builder(Matrix::list::<4, 2>()).index().column(0).name(Some("Hello World".into())).transpose().build(), + "+-------------+-----+-----+-----+-----+" + "| Hello World | 0 | 1 | 2 | 3 |" + "+-------------+-----+-----+-----+-----+" + "| column 0 | 0-0 | 1-0 | 2-0 | 3-0 |" + "+-------------+-----+-----+-----+-----+" + "| column 1 | 0-1 | 1-1 | 2-1 | 3-1 |" + "+-------------+-----+-----+-----+-----+" +); + +test_table!( + builder_index_with_no_columns, + Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]).index().build(), + "+---+---+---+---+" + "| | 0 | 1 | 2 |" + "+---+---+---+---+" + "| 0 | 1 | 2 | 3 |" + "+---+---+---+---+" + "| 1 | a | b | c |" + "+---+---+---+---+" + "| 2 | d | e | f |" + "+---+---+---+---+" +); + +test_table!( + builder_index_with_no_columns_and_name, + Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]) + .index() + .name(Some("Hello World".into())) + .build(), + "+-------------+---+---+---+" + "| | 0 | 1 | 2 |" + "+-------------+---+---+---+" + "| Hello World | | | |" + "+-------------+---+---+---+" + "| 0 | 1 | 2 | 3 |" + "+-------------+---+---+---+" + "| 1 | a | b | c |" + "+-------------+---+---+---+" + "| 2 | d | e | f |" + "+-------------+---+---+---+" +); + +test_table!( + builder_index_with_no_columns_transpose, + Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]) + .index() + .transpose() + .build(), + "+---+---+---+---+" + "| | 0 | 1 | 2 |" + "+---+---+---+---+" + "| 0 | 1 | a | d |" + "+---+---+---+---+" + "| 1 | 2 | b | e |" + "+---+---+---+---+" + "| 2 | 3 | c | f |" + "+---+---+---+---+" +); + +test_table!(builder_index_empty, Builder::default().index().build(), ""); + +test_table!( + builder_index_transpose_empty, + Builder::default().index().transpose().build(), + "" +); + +test_table!( + builder_index_invalid_dosnt_panic, + Builder::default().index().column(100).build(), + "" +); + +test_table!( + builder_index_name_doesnt_shown_when_empty, + Builder::default() + .index() + .name(Some("Hello World".into())) + .build(), + "" +); + +#[test] +fn builder_index_transpose_transpose() { + let data = Matrix::list::<4, 2>(); + let builder = Table::builder(data).index(); + + let orig_table = builder.clone().build().to_string(); + let two_times_transposed_table = builder.transpose().transpose().build().to_string(); + + assert_eq!(orig_table, two_times_transposed_table,); +} + +#[test] +fn builder_index_no_name_transpose_transpose() { + let data = Matrix::list::<4, 2>(); + let builder = Table::builder(data).index().name(None); + + let orig_table = builder.clone().build().to_string(); + let two_times_transposed_table = builder.transpose().transpose().build().to_string(); + + assert_eq!(orig_table, two_times_transposed_table,); +} diff --git a/vendor/tabled/tests/core/iter_table.rs b/vendor/tabled/tests/core/iter_table.rs new file mode 100644 index 000000000..dd38ec71c --- /dev/null +++ b/vendor/tabled/tests/core/iter_table.rs @@ -0,0 +1,214 @@ +#![cfg(feature = "std")] + +use tabled::tables::IterTable; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + iter_table, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()), + "+-----+-----+-----+" + "| 0-0 | 0-1 | 0-2 |" + "+-----+-----+-----+" + "| 1-0 | 1-1 | 1-2 |" + "+-----+-----+-----+" + "| 2-0 | 2-1 | 2-2 |" + "+-----+-----+-----+" +); + +test_table!( + iter_table_cols, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3), + "+-----+-----+-----+" + "| 0-0 | 0-1 | 0-2 |" + "+-----+-----+-----+" + "| 1-0 | 1-1 | 1-2 |" + "+-----+-----+-----+" + "| 2-0 | 2-1 | 2-2 |" + "+-----+-----+-----+" +); + +test_table!( + iter_table_cols_less, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(2), + "+-----+-----+" + "| 0-0 | 0-1 |" + "+-----+-----+" + "| 1-0 | 1-1 |" + "+-----+-----+" + "| 2-0 | 2-1 |" + "+-----+-----+" +); + +test_table!( + iter_table_cols_zero, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(0), + "" +); + +test_table!( + iter_table_iterator, + { + let mut buf = String::new(); + IterTable::new((0..3).map(|i: usize| (0..5).map(move |j: usize| format!("{i},{j}")))).fmt(&mut buf).unwrap(); + buf + }, + "+-----+-----+-----+-----+-----+" + "| 0,0 | 0,1 | 0,2 | 0,3 | 0,4 |" + "+-----+-----+-----+-----+-----+" + "| 1,0 | 1,1 | 1,2 | 1,3 | 1,4 |" + "+-----+-----+-----+-----+-----+" + "| 2,0 | 2,1 | 2,2 | 2,3 | 2,4 |" + "+-----+-----+-----+-----+-----+" +); + +test_table!( + iter_table_width, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).width(2), + "+----+----+----+" + "| 0- | 0- | 0- |" + "+----+----+----+" + "| 1- | 1- | 1- |" + "+----+----+----+" + "| 2- | 2- | 2- |" + "+----+----+----+" +); + +test_table!( + iter_table_height_does_not_work, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).height(5), + "+-----+-----+-----+" + "| 0-0 | 0-1 | 0-2 |" + "| | | |" + "| | | |" + "| | | |" + "| | | |" + "+-----+-----+-----+" + "| 1-0 | 1-1 | 1-2 |" + "| | | |" + "| | | |" + "| | | |" + "| | | |" + "+-----+-----+-----+" + "| 2-0 | 2-1 | 2-2 |" + "| | | |" + "| | | |" + "| | | |" + "| | | |" + "+-----+-----+-----+" +); + +test_table!( + iter_table_sniff_0, + IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).sniff(0), + "" +); + +test_table!( + iter_table_multiline, + IterTable::new( + vec![ + vec!["0", "1", "2", "3"], + vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"] + ] + ), + "+---+---+---+---+" + "| 0 | 1 | 2 | 3 |" + "+---+---+---+---+" + "| 0 | 0 | 0 | 0 |" + "| 1 | 1 | 1 | 1 |" + "| 2 | 2 | 2 | 2 |" + "| 3 | | 3 | |" + "| 4 | | 4 | |" + "| | 3 | | 3 |" + "| | 4 | | 4 |" + "| | | | |" + "+---+---+---+---+" +); + +test_table!( + iter_table_multiline_sniff_1, + IterTable::new( + vec![ + vec!["0", "1", "2", "3"], + vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"] + ] + ) + .sniff(1), + "+---+---+---+---+\n| 0 | 1 | 2 | 3 |\n+---+---+---+---+\n| 0\n1\n2\n3\n4 | 0\n1\n2\n\n\n3\n4 | 0\n1\n2\n3\n4\n\n\n | 0\n1\n2\n\n\n3\n4\n |\n+---+---+---+---+" +); + +test_table!( + iter_table_multiline_sniff_2, + IterTable::new( + vec![ + vec!["0", "1", "2", "3"], + vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"], + vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"], + ] + ) + .sniff(2), + "+---+---+---+---+\n| 0 | 1 | 2 | 3 |\n+---+---+---+---+\n| 0 | 0 | 0 | 0 |\n| 1 | 1 | 1 | 1 |\n| 2 | 2 | 2 | 2 |\n| 3 | | 3 | |\n| 4 | | 4 | |\n| | 3 | | 3 |\n| | 4 | | 4 |\n| | | | |\n+---+---+---+---+\n| 0\n1\n2\n3\n4 | 0\n1\n2\n\n\n3\n4 | 0\n1\n2\n3\n4\n\n\n | 0\n1\n2\n\n\n3\n4\n |\n+---+---+---+---+" +); + +test_table!( + iter_table_multiline_height_work, + IterTable::new( + vec![ + vec!["0", "1", "2", "3"], + vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"] + ] + ) + .height(3) + , + "+---+---+---+---+" + "| 0 | 1 | 2 | 3 |" + "| | | | |" + "| | | | |" + "+---+---+---+---+" + "| 0 | 0 | 0 | 0 |" + "| 1 | 1 | 1 | 1 |" + "| 2 | 2 | 2 | 2 |" + "+---+---+---+---+" +); + +test_table!( + iter_table_sniff_cut, + IterTable::new( + vec![ + vec!["12", "12", "22", "32"], + vec!["0", "0", "0", "0"], + vec!["023", "123", "223", "323"], + ] + ) + .sniff(2) + , + "+----+----+----+----+" + "| 12 | 12 | 22 | 32 |" + "+----+----+----+----+" + "| 0 | 0 | 0 | 0 |" + "+----+----+----+----+" + "| 02 | 12 | 22 | 32 |" + "+----+----+----+----+" +); + +test_table!( + iter_table_sniff, + IterTable::new( + vec![ + vec!["023", "123", "223", "323"], + vec!["12", "12", "22", "32"], + vec!["0", "0", "0", "0"], + ] + ) + .sniff(2) + , + "+-----+-----+-----+-----+" + "| 023 | 123 | 223 | 323 |" + "+-----+-----+-----+-----+" + "| 12 | 12 | 22 | 32 |" + "+-----+-----+-----+-----+" + "| 0 | 0 | 0 | 0 |" + "+-----+-----+-----+-----+" +); diff --git a/vendor/tabled/tests/core/mod.rs b/vendor/tabled/tests/core/mod.rs new file mode 100644 index 000000000..14a5404b7 --- /dev/null +++ b/vendor/tabled/tests/core/mod.rs @@ -0,0 +1,7 @@ +mod builder_test; +mod compact_table; +mod extended_table_test; +mod index_test; +mod iter_table; +mod pool_table; +mod table_test; diff --git a/vendor/tabled/tests/core/pool_table.rs b/vendor/tabled/tests/core/pool_table.rs new file mode 100644 index 000000000..ee28dae78 --- /dev/null +++ b/vendor/tabled/tests/core/pool_table.rs @@ -0,0 +1,634 @@ +#![cfg(feature = "std")] + +use tabled::{ + grid::dimension::{DimensionPriority, PoolTableDimension}, + settings::{formatting::AlignmentStrategy, Alignment, Margin, Padding, Style}, + tables::{PoolTable, TableValue}, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +#[cfg(feature = "color")] +use tabled::grid::color::StaticColor; + +test_table!( + pool_table, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()), + "+-----+-----+-----+" + "| 0-0 | 0-1 | 0-2 |" + "+-----+-----+-----+" + "| 1-0 | 1-1 | 1-2 |" + "+-----+-----+-----+" + "| 2-0 | 2-1 | 2-2 |" + "+-----+-----+-----+" +); + +test_table!( + pool_table_1, + PoolTable::new([vec!["111111", "222"], vec!["111", "2233", "1", "2", "3"]]), + "+-------------+----------+" + "| 111111 | 222 |" + "+-----+------++--+---+---+" + "| 111 | 2233 | 1 | 2 | 3 |" + "+-----+------+---+---+---+" +); + +test_table!( + pool_table_2, + PoolTable::new([vec!["111", "2233", "1", "2", "3"], vec!["111111", "222"]]), + "+-----+------+---+---+---+" + "| 111 | 2233 | 1 | 2 | 3 |" + "+-----+------++--+---+---+" + "| 111111 | 222 |" + "+-------------+----------+" +); + +test_table!( + pool_table_3, + PoolTable::new([vec!["1\n11", "2\n2\n3\n3", "1", "\n2\n", "3"], vec!["11\n111\n1", "2\n2\n2"]]), + "+----+---+---+---+---+" + "| 1 | 2 | 1 | | 3 |" + "| 11 | 2 | | 2 | |" + "| | 3 | | | |" + "| | 3 | | | |" + "+----+---+--++---+---+" + "| 11 | 2 |" + "| 111 | 2 |" + "| 1 | 2 |" + "+-----------+--------+" +); + +test_table!( + pool_table_4, + PoolTable::new([vec!["11\n111\n1", "2\n2\n2"], vec!["1\n11", "2\n2\n3\n3", "1", "\n2\n", "3"]]), + "+-----------+--------+" + "| 11 | 2 |" + "| 111 | 2 |" + "| 1 | 2 |" + "+----+---+--++---+---+" + "| 1 | 2 | 1 | | 3 |" + "| 11 | 2 | | 2 | |" + "| | 3 | | | |" + "| | 3 | | | |" + "+----+---+---+---+---+" +); + +test_table!( + pool_table_multiline, + PoolTable::new([ + ["1", "2\n2", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3", "2\n2"] + ]), + "+---+---+---+" + "| 1 | 2 | 3 |" + "| | 2 | 3 |" + "| | | 3 |" + "+---+---+---+" + "| 3 | 2 | 1 |" + "| 3 | 2 | |" + "| 3 | | |" + "+---+---+---+" + "| 1 | 3 | 2 |" + "| | 3 | 2 |" + "| | 3 | |" + "+---+---+---+" +); + +test_table!( + pool_table_value, + PoolTable::from(TableValue::Row(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), + TableValue::Column(vec![TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2"))]), + TableValue::Column(vec![TableValue::Cell(String::from("2-0")), TableValue::Cell(String::from("2-1")), TableValue::Cell(String::from("2-2"))]), + ])) + .with(Style::modern()), + "┌─────┬─────┬─────┐" + "│ 0-0 │ 1-0 │ 2-0 │" + "├─────┼─────┼─────┤" + "│ 0-1 │ 1-1 │ 2-1 │" + "├─────┼─────┼─────┤" + "│ 0-2 │ 1-2 │ 2-2 │" + "└─────┴─────┴─────┘" +); + +test_table!( + pool_table_value_1, + PoolTable::from(TableValue::Row(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), + TableValue::Column(vec![TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2"))]), + TableValue::Column(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("2-1")), + TableValue::Cell(String::from("2-2")), + ]), + ])) + .with(Style::modern()), + "┌─────┬─────┬──────┐" + "│ 0-0 │ 1-0 │ 2-01 │" + "│ │ ├──────┤" + "│ │ │ 2-02 │" + "├─────┼─────┼──────┤" + "│ 0-1 │ 1-1 │ 2-03 │" + "│ │ ├──────┤" + "├─────┼─────┤ 2-1 │" + "│ 0-2 │ 1-2 ├──────┤" + "│ │ │ 2-2 │" + "└─────┴─────┴──────┘" +); + +test_table!( + pool_table_value_2, + PoolTable::from(TableValue::Row(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), + TableValue::Column(vec![ + TableValue::Row(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("1-1")), + TableValue::Cell(String::from("1-2")) + ]), + TableValue::Column(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("2-1")), + TableValue::Cell(String::from("2-2")) + ]), + ])) + .with(Style::modern()), + "┌─────┬──────┬──────┬──────┬──────┐" + "│ 0-0 │ 2-01 │ 2-02 │ 2-03 │ 2-01 │" + "│ ├──────┴──────┴──────┤ │" + "│ │ 2-01 ├──────┤" + "├─────┼────────────────────┤ 2-02 │" + "│ 0-1 │ 2-02 ├──────┤" + "│ ├────────────────────┤ 2-03 │" + "│ │ 2-03 ├──────┤" + "├─────┼────────────────────┤ 2-1 │" + "│ 0-2 │ 1-1 │ │" + "│ ├────────────────────┼──────┤" + "│ │ 1-2 │ 2-2 │" + "└─────┴────────────────────┴──────┘" +); + +test_table!( + pool_table_value_3, + PoolTable::from(TableValue::Row(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), + TableValue::Column(vec![ + TableValue::Row(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("1-1")), + TableValue::Row(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("1-2")) + ]), + TableValue::Column(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("2-\n0\n1")), TableValue::Cell(String::from("2\n-\n0\n2")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("2-1")), + TableValue::Column(vec![TableValue::Cell(String::from("2-0\n1")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("2-2")) + ]), + ])) + .with(Style::modern()), + "┌─────┬──────┬──────┬──────┬──────┐" + "│ 0-0 │ 2-01 │ 2-02 │ 2-03 │ 2- │" + "│ │ │ │ │ 0 │" + "│ │ │ │ │ 1 │" + "│ ├──────┴──────┴──────┼──────┤" + "│ │ 2-01 │ 2 │" + "│ │ │ - │" + "│ ├────────────────────┤ 0 │" + "├─────┤ 2-02 │ 2 │" + "│ 0-1 │ ├──────┤" + "│ ├────────────────────┤ 2-03 │" + "│ │ 2-03 ├──────┤" + "│ ├────────────────────┤ 2-1 │" + "│ │ 1-1 ├──────┤" + "│ │ │ 2-0 │" + "├─────┤ │ 1 │" + "│ 0-2 ├──────┬──────┬──────┼──────┤" + "│ │ 2-01 │ 2-02 │ 2-03 │ 2-02 │" + "│ │ │ │ ├──────┤" + "│ ├──────┴──────┴──────┤ 2-03 │" + "│ │ 1-2 ├──────┤" + "│ │ │ 2-2 │" + "└─────┴────────────────────┴──────┘" +); + +test_table!( + pool_table_example, + { + let data = vec![ + vec!["Hello World", "Hello World", "Hello World"], + vec!["Hello", "", "Hello"], + vec!["W", "o", "r", "l", "d"], + ]; + + let data = TableValue::Column( + data.into_iter() + .map(|row| { + TableValue::Row( + row.into_iter() + .map(|text| TableValue::Cell(text.to_owned())) + .collect(), + ) + }) + .collect(), + ); + + PoolTable::from(data) + .with(Style::modern()) + .with(Alignment::center()) + .to_string() + }, + "┌─────────────┬─────────────┬─────────────┐" + "│ Hello World │ Hello World │ Hello World │" + "├─────────────┴─┬──────────┬┴─────────────┤" + "│ Hello │ │ Hello │" + "├────────┬──────┴─┬───────┬┴──────┬───────┤" + "│ W │ o │ r │ l │ d │" + "└────────┴────────┴───────┴───────┴───────┘" +); + +test_table!( + pool_table_value_empty_row, + PoolTable::from(TableValue::Row(vec![])) + .with(Style::modern()), + "┌──┐" + "│ │" + "└──┘" +); + +test_table!( + pool_table_value_empty_column, + PoolTable::from(TableValue::Column(vec![])) + .with(Style::modern()), + "┌──┐" + "│ │" + "└──┘" +); + +test_table!( + pool_table_value_empty_cell, + PoolTable::from(TableValue::Cell(String::from(""))) + .with(Style::modern()), + "┌──┐" + "│ │" + "└──┘" +); + +test_table!( + pool_table_padding, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Padding::new(1, 2, 3, 4)), + "+------+------+------+" + "| | | |" + "| | | |" + "| | | |" + "| 0-0 | 0-1 | 0-2 |" + "| | | |" + "| | | |" + "| | | |" + "| | | |" + "+------+------+------+" + "| | | |" + "| | | |" + "| | | |" + "| 1-0 | 1-1 | 1-2 |" + "| | | |" + "| | | |" + "| | | |" + "| | | |" + "+------+------+------+" + "| | | |" + "| | | |" + "| | | |" + "| 2-0 | 2-1 | 2-2 |" + "| | | |" + "| | | |" + "| | | |" + "| | | |" + "+------+------+------+" +); + +#[cfg(feature = "color")] +test_table!( + pool_table_padding_2, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()) + .with(Padding::new(1, 2, 3, 4) + .fill('!', '@', '#', '$') + .colorize( + StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), + StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), + StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), + StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), + ) + ), + "+------+------+------+" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m!\u{1b}[39m0-0\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m0-1\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m0-2\u{1b}[34m@@\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "+------+------+------+" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m!\u{1b}[39m1-0\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m1-1\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m1-2\u{1b}[34m@@\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "+------+------+------+" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" + "|\u{1b}[34m!\u{1b}[39m2-0\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m2-1\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m2-2\u{1b}[34m@@\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" + "+------+------+------+" +); + +test_table!( + pool_table_margin, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Margin::new(1, 2, 3, 4).fill('!', '@', '#', '$')), + "!###################@@" + "!###################@@" + "!###################@@" + "!+-----+-----+-----+@@" + "!| 0-0 | 0-1 | 0-2 |@@" + "!+-----+-----+-----+@@" + "!| 1-0 | 1-1 | 1-2 |@@" + "!+-----+-----+-----+@@" + "!| 2-0 | 2-1 | 2-2 |@@" + "!+-----+-----+-----+@@" + "!$$$$$$$$$$$$$$$$$$$@@" + "!$$$$$$$$$$$$$$$$$$$@@" + "!$$$$$$$$$$$$$$$$$$$@@" + "!$$$$$$$$$$$$$$$$$$$@@" +); + +test_table!( + pool_table_alignment_bottom, + PoolTable::new([ + ["1", "2\n2", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3", "2\n2"] + ]) + .with(Alignment::bottom()), + "+---+---+---+" + "| | | 3 |" + "| | 2 | 3 |" + "| 1 | 2 | 3 |" + "+---+---+---+" + "| 3 | | |" + "| 3 | 2 | |" + "| 3 | 2 | 1 |" + "+---+---+---+" + "| | 3 | |" + "| | 3 | 2 |" + "| 1 | 3 | 2 |" + "+---+---+---+" +); + +test_table!( + pool_table_alignment_center_vertical, + PoolTable::new([ + ["1", "2\n2", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3", "2\n2"] + ]) + .with(Alignment::center_vertical()), + "+---+---+---+" + "| | 2 | 3 |" + "| 1 | 2 | 3 |" + "| | | 3 |" + "+---+---+---+" + "| 3 | 2 | |" + "| 3 | 2 | 1 |" + "| 3 | | |" + "+---+---+---+" + "| | 3 | 2 |" + "| 1 | 3 | 2 |" + "| | 3 | |" + "+---+---+---+" +); + +test_table!( + pool_table_alignment_right, + PoolTable::new([ + ["1 ", "2\n2 ", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3 ", "2\n2"] + ]) + .with(Alignment::right()), + "+------------------------+----------+---+" + "| 1 | 2 | 3 |" + "| | 2 | 3 |" + "| | | 3 |" + "+-------------+----------+-+--------+---+" + "| 3 | 2 | 1 |" + "| 3 | 2 | |" + "| 3 | | |" + "+------+------+------------+-----+------+" + "| 1 | 3 | 2 |" + "| | 3 | 2 |" + "| | 3 | |" + "+------+-------------------------+------+" +); + +test_table!( + pool_table_alignment_right_per_line, + PoolTable::new([ + ["1 ", "2\n2 ", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3 ", "2\n2"] + ]) + .with(Alignment::right()) + .with(AlignmentStrategy::PerLine), + "+------------------------+----------+---+" + "| 1 | 2 | 3 |" + "| | 2 | 3 |" + "| | | 3 |" + "+-------------+----------+-+--------+---+" + "| 3 | 2 | 1 |" + "| 3 | 2 | |" + "| 3 | | |" + "+------+------+------------+-----+------+" + "| 1 | 3 | 2 |" + "| | 3 | 2 |" + "| | 3 | |" + "+------+-------------------------+------+" +); + +test_table!( + pool_table_alignment_center_horizontal, + PoolTable::new([ + ["1 ", "2\n2 ", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3 ", "2\n2"] + ]) + .with(Alignment::center()), + "+------------------------+----------+---+" + "| 1 | 2 | 3 |" + "| | 2 | 3 |" + "| | | 3 |" + "+-------------+----------+-+--------+---+" + "| 3 | 2 | 1 |" + "| 3 | 2 | |" + "| 3 | | |" + "+------+------+------------+-----+------+" + "| 1 | 3 | 2 |" + "| | 3 | 2 |" + "| | 3 | |" + "+------+-------------------------+------+" +); + +test_table!( + pool_table_alignment_center_horizontal_line_strategy, + PoolTable::new([ + ["1 ", "2\n22222", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3 ", "2\n2"] + ]) + .with(Alignment::center()) + .with(AlignmentStrategy::PerLine), + "+------------------------+-------+---+" + "| 1 | 2 | 3 |" + "| | 22222 | 3 |" + "| | | 3 |" + "+------------+-----------+-------+---+" + "| 3 | 2 | 1 |" + "| 3 | 2 | |" + "| 3 | | |" + "+-----+------+-----------+-----+-----+" + "| 1 | 3 | 2 |" + "| | 3 | 2 |" + "| | 3 | |" + "+-----+------------------------+-----+" +); + +test_table!( + pool_table_style_empty, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Style::empty()), + " 0-0 0-1 0-2 " + " 1-0 1-1 1-2 " + " 2-0 2-1 2-2 " +); + +test_table!( + pool_table_style_markdown, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Style::markdown()), + "| 0-0 | 0-1 | 0-2 |" + "| 1-0 | 1-1 | 1-2 |" + "| 2-0 | 2-1 | 2-2 |" +); + +test_table!( + pool_table_style_rounded, + PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Style::rounded()), + "╭─────┬─────┬─────╮" + "│ 0-0 │ 0-1 │ 0-2 │" + " ───── ───── ───── " + "│ 1-0 │ 1-1 │ 1-2 │" + " ───── ───── ───── " + "│ 2-0 │ 2-1 │ 2-2 │" + "╰─────┴─────┴─────╯" +); + +test_table!( + pool_table_dim_ctrl_0, + PoolTable::from(TableValue::Row(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), + TableValue::Column(vec![TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2"))]), + TableValue::Column(vec![ + TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), + TableValue::Cell(String::from("2-1")), + TableValue::Cell(String::from("2-2")), + ]), + ])) + .with(PoolTableDimension::new(DimensionPriority::Last, DimensionPriority::Last)), + "+-----+-----+------+" + "| 0-0 | 1-0 | 2-01 |" + "+-----+-----+------+" + "| 0-1 | 1-1 | 2-02 |" + "+-----+-----+------+" + "| 0-2 | 1-2 | 2-03 |" + "| | +------+" + "| | | 2-1 |" + "| | +------+" + "| | | 2-2 |" + "+-----+-----+------+" +); + +test_table!( + pool_table_dim_ctrl_1, + PoolTable::new([ + ["1 ", "2\n2 ", "3\n3\n3"], + ["3\n3\n3", "2\n2", "1"], + ["1", "3\n3\n3 ", "2\n2"] + ]) + .with(PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List)), + "+------------------------+----------+---+" + "| 1 | 2 | 3 |" + "| | 2 | 3 |" + "| | | 3 |" + "+-------------+----------+-+--------+---+" + "| 3 | 2 | 1 |" + "| 3 | 2 | |" + "| 3 | | |" + "+------+------+------------+-----+------+" + "| 1 | 3 | 2 |" + "| | 3 | 2 |" + "| | 3 | |" + "+------+-------------------------+------+" +); + +test_table!( + pool_table_2_columns_1_cell, + PoolTable::from(TableValue::Row(vec![ + TableValue::Column(vec![ + TableValue::Cell(String::from("0-0")), + TableValue::Cell(String::from("0-1")), + TableValue::Cell(String::from("0-2")), + TableValue::Cell(String::from("0-3")), + TableValue::Cell(String::from("0-4")), + TableValue::Cell(String::from("0-5")), + ]), + TableValue::Column(vec![ + TableValue::Cell(String::from("1-0")), + TableValue::Cell(String::from("1-1")), + TableValue::Cell(String::from("1-2")), + TableValue::Cell(String::from("1-3")), + TableValue::Cell(String::from("1-4")), + TableValue::Cell(String::from("1-6")), + TableValue::Cell(String::from("1-7")), + TableValue::Cell(String::from("1-8")), + TableValue::Cell(String::from("1-9")), + ]), + TableValue::Cell(String::from("2-0")), + ])) + .with(PoolTableDimension::new(DimensionPriority::Last, DimensionPriority::Last)), + "+-----+-----+-----+" + "| 0-0 | 1-0 | 2-0 |" + "+-----+-----+ |" + "| 0-1 | 1-1 | |" + "+-----+-----+ |" + "| 0-2 | 1-2 | |" + "+-----+-----+ |" + "| 0-3 | 1-3 | |" + "+-----+-----+ |" + "| 0-4 | 1-4 | |" + "+-----+-----+ |" + "| 0-5 | 1-6 | |" + "| +-----+ |" + "| | 1-7 | |" + "| +-----+ |" + "| | 1-8 | |" + "| +-----+ |" + "| | 1-9 | |" + "+-----+-----+-----+" +); diff --git a/vendor/tabled/tests/core/table_test.rs b/vendor/tabled/tests/core/table_test.rs new file mode 100644 index 000000000..b949626bb --- /dev/null +++ b/vendor/tabled/tests/core/table_test.rs @@ -0,0 +1,847 @@ +#![cfg(feature = "std")] + +use std::iter::FromIterator; + +use tabled::{ + builder::Builder, + settings::{formatting::Charset, Height, Modify, Padding, Settings, Style, Width}, + Table, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +mod default_types { + use super::*; + + test_table!( + table_str_vec, + Table::new(vec!["hello", "world"]), + "+-------+" + "| &str |" + "+-------+" + "| hello |" + "+-------+" + "| world |" + "+-------+" + ); + + test_table!( + table_char_vec, + Table::new(vec!['a', 'b', 'c']), + "+------+" + "| char |" + "+------+" + "| a |" + "+------+" + "| b |" + "+------+" + "| c |" + "+------+" + ); + + test_table!( + table_bool_vec, + Table::new(vec![true, false, true]), + "+-------+" + "| bool |" + "+-------+" + "| true |" + "+-------+" + "| false |" + "+-------+" + "| true |" + "+-------+" + ); + + test_table!( + table_usize_vec, + Table::new(vec![0usize, 1usize, 2usize]), + "+-------+" + "| usize |" + "+-------+" + "| 0 |" + "+-------+" + "| 1 |" + "+-------+" + "| 2 |" + "+-------+" + ); + + test_table!( + table_isize_vec, + Table::new(vec![0isize, 1isize, 2isize]), + "+-------+" + "| isize |" + "+-------+" + "| 0 |" + "+-------+" + "| 1 |" + "+-------+" + "| 2 |" + "+-------+" + ); + + test_table!( + table_u8_vec, + Table::new(vec![0u8, 1u8, 2u8]), + "+----+" + "| u8 |" + "+----+" + "| 0 |" + "+----+" + "| 1 |" + "+----+" + "| 2 |" + "+----+" + ); + + test_table!( + table_u16_vec, + Table::new(vec![0u16, 1u16, 2u16]), + "+-----+" + "| u16 |" + "+-----+" + "| 0 |" + "+-----+" + "| 1 |" + "+-----+" + "| 2 |" + "+-----+" + ); + + test_table!( + table_u32_vec, + Table::new(vec![0u32, 1u32, 2u32]), + "+-----+" + "| u32 |" + "+-----+" + "| 0 |" + "+-----+" + "| 1 |" + "+-----+" + "| 2 |" + "+-----+" + ); + + test_table!( + table_u64_vec, + Table::new(vec![0u64, 1u64, 2u64]), + "+-----+" + "| u64 |" + "+-----+" + "| 0 |" + "+-----+" + "| 1 |" + "+-----+" + "| 2 |" + "+-----+" + ); + + test_table!( + table_u128_vec, + Table::new(vec![0u128, 1u128, 2u128]), + "+------+" + "| u128 |" + "+------+" + "| 0 |" + "+------+" + "| 1 |" + "+------+" + "| 2 |" + "+------+" + ); + + test_table!( + table_i8_vec, + Table::new(vec![0i8, 1i8, 2i8]), + "+----+" + "| i8 |" + "+----+" + "| 0 |" + "+----+" + "| 1 |" + "+----+" + "| 2 |" + "+----+" + ); + + test_table!( + table_i16_vec, + Table::new(vec![0i16, 1, 2]), + "+-----+" + "| i16 |" + "+-----+" + "| 0 |" + "+-----+" + "| 1 |" + "+-----+" + "| 2 |" + "+-----+" + ); + + test_table!( + table_i32_vec, + Table::new(vec![0i32, 1, 2]), + "+-----+" + "| i32 |" + "+-----+" + "| 0 |" + "+-----+" + "| 1 |" + "+-----+" + "| 2 |" + "+-----+" + ); + + test_table!( + table_i64_vec, + Table::new(vec![0i64, 1, 2]), + "+-----+" + "| i64 |" + "+-----+" + "| 0 |" + "+-----+" + "| 1 |" + "+-----+" + "| 2 |" + "+-----+" + ); + + test_table!( + table_i128_vec, + Table::new(vec![0i128, 1, 2]), + "+------+" + "| i128 |" + "+------+" + "| 0 |" + "+------+" + "| 1 |" + "+------+" + "| 2 |" + "+------+" + ); + + test_table!( + table_array, + Table::new(vec![[0, 1, 2], [3, 4, 5], [6, 7, 8]]), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 3 | 4 | 5 |" + "+---+---+---+" + "| 6 | 7 | 8 |" + "+---+---+---+" + ); +} + +test_table!( + table_tuple, + Table::new(vec![("we are in", 2020)]), + "+-----------+------+" + "| &str | i32 |" + "+-----------+------+" + "| we are in | 2020 |" + "+-----------+------+" +); + +test_table!( + table_single_tuple, + Table::new(vec![(2020,)]), + "+------+" + "| i32 |" + "+------+" + "| 2020 |" + "+------+" +); + +test_table!( + table_tuple_vec, + #[allow(clippy::needless_borrow)] + Table::new(&[(0, "Monday"), (1, "Thursday")]), + "+-----+----------+" + "| i32 | &str |" + "+-----+----------+" + "| 0 | Monday |" + "+-----+----------+" + "| 1 | Thursday |" + "+-----+----------+" +); + +test_table!( + build_table_from_iterator, + Matrix::new(3, 3).with(Style::psql()), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + multiline_table_test_0, + Table::new([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) + .with(Charset::clean()) + .with(Style::modern()), + r#"┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"# + r#"│ 0 │"# + r#"├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"# + r#"│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# + r#"│ │"# + r#"│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# + r#"│ │"# + r#"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"# +); + +test_table!( + multiline_table_test_1, + Table::new([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) + .with(Charset::clean()) + .with(Style::modern()) + .with(Width::wrap(100)), + "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐" + "│ 0 │" + "├──────────────────────────────────────────────────────────────────────────────────────────────────┤" + "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │" + "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" + "│ │" + "│ For convenience, we are providing full build │" + "│ s for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have │" + "│ the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/inst │" + "│ allation.md#dependencies │" + "│ │" + "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" +); + +test_table!( + multiline_table_test_2, + Table::new([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) + .with(Modify::new((1, 0)).with(Charset::clean())) + .with(Style::modern()) + .with(Width::wrap(100)), + "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐" + "│ 0 │" + "├──────────────────────────────────────────────────────────────────────────────────────────────────┤" + "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │" + "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" + "│ │" + "│ For convenience, we are providing full build │" + "│ s for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have │" + "│ the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/inst │" + "│ allation.md#dependencies │" + "│ │" + "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" +); + +#[cfg(feature = "derive")] +mod derived { + use super::*; + + use std::collections::{BTreeMap, BTreeSet}; + + use tabled::Tabled; + + #[derive(Tabled)] + struct TestType { + f1: u8, + f2: &'static str, + } + + test_table!( + table_vector_structures, + Table::new([TestType { f1: 0, f2: "0" }, TestType { f1: 1, f2: "1" }]), + "+----+----+" + "| f1 | f2 |" + "+----+----+" + "| 0 | 0 |" + "+----+----+" + "| 1 | 1 |" + "+----+----+" + ); + + test_table!( + table_empty_vector_structures, + Table::new({let v: Vec<TestType> = Vec::new(); v}), + "+----+----+" + "| f1 | f2 |" + "+----+----+" + ); + + test_table!( + table_option, + Table::new(Some(TestType { f1: 0, f2: "0" })), + "+----+----+" + "| f1 | f2 |" + "+----+----+" + "| 0 | 0 |" + "+----+----+" + ); + + test_table!( + table_option_none, + Table::new(Option::<TestType>::None), + "+----+----+" + "| f1 | f2 |" + "+----+----+" + ); + + test_table!( + table_tuple_with_structure_vec, + Table::new([(0, TestType { f1: 0, f2: "0str" }), (1, TestType { f1: 1, f2: "1str" })]), + "+-----+----+------+" + "| i32 | f1 | f2 |" + "+-----+----+------+" + "| 0 | 0 | 0str |" + "+-----+----+------+" + "| 1 | 1 | 1str |" + "+-----+----+------+" + ); + + test_table!( + table_vector_structures_with_hidden_tabled, + Table::new({ + #[derive(Tabled)] + struct St { + #[allow(dead_code)] + #[tabled(skip)] + f1: u8, + f2: &'static str, + } + + vec![St { f1: 0, f2: "0" }, St { f1: 1, f2: "1" }] + }), + "+----+" + "| f2 |" + "+----+" + "| 0 |" + "+----+" + "| 1 |" + "+----+" + ); + + test_table!( + table_enum, + Table::new({ + #[derive(Tabled)] + enum Letters { + Vowels { character: char, lang: u8 }, + Consonant(char), + Digit, + } + + vec![ + Letters::Vowels { + character: 'a', + lang: 0, + }, + Letters::Consonant('w'), + Letters::Vowels { + character: 'b', + lang: 1, + }, + Letters::Vowels { + character: 'c', + lang: 2, + }, + Letters::Digit, + ] + }), + "+--------+-----------+-------+" + "| Vowels | Consonant | Digit |" + "+--------+-----------+-------+" + "| + | | |" + "+--------+-----------+-------+" + "| | + | |" + "+--------+-----------+-------+" + "| + | | |" + "+--------+-----------+-------+" + "| + | | |" + "+--------+-----------+-------+" + "| | | + |" + "+--------+-----------+-------+" + ); + + test_table!( + table_enum_with_hidden_variant, + Table::new({ + #[allow(dead_code)] + #[derive(Tabled)] + enum Letters { + Vowels { + character: char, + lang: u8, + }, + Consonant(char), + #[tabled(skip)] + Digit, + } + + vec![ + Letters::Vowels { + character: 'a', + lang: 0, + }, + Letters::Consonant('w'), + Letters::Vowels { + character: 'b', + lang: 1, + }, + Letters::Vowels { + character: 'c', + lang: 2, + }, + Letters::Digit, + ] + }), + "+--------+-----------+" + "| Vowels | Consonant |" + "+--------+-----------+" + "| + | |" + "+--------+-----------+" + "| | + |" + "+--------+-----------+" + "| + | |" + "+--------+-----------+" + "| + | |" + "+--------+-----------+" + "| | |" + "+--------+-----------+" + ); + + test_table!( + table_btreemap, + Table::new({ + #[derive(Tabled)] + struct A { + b: u8, + c: &'static str, + } + + let mut map = BTreeMap::new(); + map.insert(0, A { b: 1, c: "s1" }); + map.insert(1, A { b: 2, c: "s2" }); + map.insert(3, A { b: 3, c: "s3" }); + + map + }), + "+-----+---+----+" + "| i32 | b | c |" + "+-----+---+----+" + "| 0 | 1 | s1 |" + "+-----+---+----+" + "| 1 | 2 | s2 |" + "+-----+---+----+" + "| 3 | 3 | s3 |" + "+-----+---+----+" + ); + + test_table!( + table_emojie_utf8_style, + Table::new({ + #[derive(Tabled)] + struct Language { + name: &'static str, + designed_by: &'static str, + invented_year: usize, + } + + vec![ + Language { + name: "C 💕", + designed_by: "Dennis Ritchie", + invented_year: 1972, + }, + Language { + name: "Rust 👍", + designed_by: "Graydon Hoare", + invented_year: 2010, + }, + Language { + name: "Go 🧋", + designed_by: "Rob Pike", + invented_year: 2009, + }, + ] + }).with(Style::modern().remove_horizontal()), + // Note: It doesn't look good in VS Code + "┌─────────┬────────────────┬───────────────┐" + "│ name │ designed_by │ invented_year │" + "│ C 💕 │ Dennis Ritchie │ 1972 │" + "│ Rust 👍 │ Graydon Hoare │ 2010 │" + "│ Go 🧋 │ Rob Pike │ 2009 │" + "└─────────┴────────────────┴───────────────┘" + ); + + test_table!( + table_btreeset, + Table::new({ + #[derive(Tabled, PartialEq, Eq, PartialOrd, Ord)] + struct A { + b: u8, + c: &'static str, + } + + let mut map = BTreeSet::new(); + map.insert(A { b: 1, c: "s1" }); + map.insert(A { b: 2, c: "s2" }); + map.insert(A { b: 3, c: "s3" }); + + map + }), + "+---+----+" + "| b | c |" + "+---+----+" + "| 1 | s1 |" + "+---+----+" + "| 2 | s2 |" + "+---+----+" + "| 3 | s3 |" + "+---+----+" + ); + + test_table!( + table_emojie, + Table::new({ + #[derive(Tabled)] + struct Language { + name: &'static str, + designed_by: &'static str, + invented_year: usize, + } + + vec![ + Language { + name: "C 💕", + designed_by: "Dennis Ritchie", + invented_year: 1972, + }, + Language { + name: "Rust 👍", + designed_by: "Graydon Hoare", + invented_year: 2010, + }, + Language { + name: "Go 🧋", + designed_by: "Rob Pike", + invented_year: 2009, + }, + ] + }), + "+---------+----------------+---------------+" + "| name | designed_by | invented_year |" + "+---------+----------------+---------------+" + "| C 💕 | Dennis Ritchie | 1972 |" + "+---------+----------------+---------------+" + "| Rust 👍 | Graydon Hoare | 2010 |" + "+---------+----------------+---------------+" + "| Go 🧋 | Rob Pike | 2009 |" + "+---------+----------------+---------------+" + ); + + test_table!( + tuple_combination, + Table::new({ + #[derive(Tabled)] + enum Domain { + Security, + Embedded, + Frontend, + Unknown, + } + + #[derive(Tabled)] + struct Developer(#[tabled(rename = "name")] &'static str); + + vec![ + (Developer("Terri Kshlerin"), Domain::Embedded), + (Developer("Catalina Dicki"), Domain::Security), + (Developer("Jennie Schmeler"), Domain::Frontend), + (Developer("Maxim Zhiburt"), Domain::Unknown), + ] + }), + "+-----------------+----------+----------+----------+---------+" + "| name | Security | Embedded | Frontend | Unknown |" + "+-----------------+----------+----------+----------+---------+" + "| Terri Kshlerin | | + | | |" + "+-----------------+----------+----------+----------+---------+" + "| Catalina Dicki | + | | | |" + "+-----------------+----------+----------+----------+---------+" + "| Jennie Schmeler | | | + | |" + "+-----------------+----------+----------+----------+---------+" + "| Maxim Zhiburt | | | | + |" + "+-----------------+----------+----------+----------+---------+" + + ); + + test_table!( + table_trait, + Table::new({ + #[derive(Tabled)] + enum Domain { + Security, + Embedded, + Frontend, + Unknown, + } + + #[derive(Tabled)] + struct Developer(#[tabled(rename = "name")] &'static str); + + vec![ + (Developer("Terri Kshlerin"), Domain::Embedded), + (Developer("Catalina Dicki"), Domain::Security), + (Developer("Jennie Schmeler"), Domain::Frontend), + (Developer("Maxim Zhiburt"), Domain::Unknown), + ] + }), + "+-----------------+----------+----------+----------+---------+" + "| name | Security | Embedded | Frontend | Unknown |" + "+-----------------+----------+----------+----------+---------+" + "| Terri Kshlerin | | + | | |" + "+-----------------+----------+----------+----------+---------+" + "| Catalina Dicki | + | | | |" + "+-----------------+----------+----------+----------+---------+" + "| Jennie Schmeler | | | + | |" + "+-----------------+----------+----------+----------+---------+" + "| Maxim Zhiburt | | | | + |" + "+-----------------+----------+----------+----------+---------+" + ); + + test_table!( + table_emojie_multiline, + Table::new({ + #[derive(Tabled)] + struct Article { + name: &'static str, + author: &'static str, + text: &'static str, + rating: usize, + } + + vec![ + Article { + name: "Rebase vs Merge commit in depth 👋", + author: "Rose Kuphal DVM", + text: "A multiline\n text with 🤯 😳 🥵 🥶\n a bunch of emojies ☄️ 💥 🔥 🌪", + rating: 43, + }, + Article { + name: "Keep it simple", + author: "Unknown", + text: "🍳", + rating: 100, + }, + ] + }), + "+------------------------------------+-----------------+-------------------------------+--------+" + "| name | author | text | rating |" + "+------------------------------------+-----------------+-------------------------------+--------+" + "| Rebase vs Merge commit in depth 👋 | Rose Kuphal DVM | A multiline | 43 |" + "| | | text with 🤯 😳 🥵 🥶 | |" + "| | | a bunch of emojies ☄\u{fe0f} 💥 🔥 🌪 | |" + "+------------------------------------+-----------------+-------------------------------+--------+" + "| Keep it simple | Unknown | 🍳 | 100 |" + "+------------------------------------+-----------------+-------------------------------+--------+" + ); +} + +#[cfg(feature = "color")] +#[test] +fn multiline_table_test2() { + use testing_table::assert_table; + + let data = &[ + ["\u{1b}[37mThis is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\n\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\n\u{1b}[0m"], + ]; + + let mut table = Table::new(data); + table.with(Style::modern()); + + assert_table!( + ansi_str::AnsiStr::ansi_strip(&table.to_string()), + r#"┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"# + r#"│ 0 │"# + r#"├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"# + r#"│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# + r#"│ │"# + r#"│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# + r#"│ │"# + r#"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"# + ); + + table.with(Width::wrap(100)); + + assert_table!( + ansi_str::AnsiStr::ansi_strip(&table.to_string()), + "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐" + "│ 0 │" + "├──────────────────────────────────────────────────────────────────────────────────────────────────┤" + "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │" + "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" + "│ │" + "│ For convenience, we are providing full build │" + "│ s for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have │" + "│ the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/inst │" + "│ allation.md#dependencies │" + "│ │" + "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" + ); +} + +test_table!( + table_1x1_empty, + { + Builder::from_iter(vec![vec![""]]).build() + .with(Style::modern()) + .with(Settings::new(Height::limit(0), Width::increase(10))) + }, + "┌────────┐" + "└────────┘" +); + +test_table!( + table_2x2_empty, + { + Builder::from_iter(vec![vec![" ", ""], vec![" ", ""]]).build() + .with(Style::modern()) + .with(Padding::zero()) + .with(Height::list([1, 0])) + }, + "┌─┬┐" + "│ ││" + "├─┼┤" + "└─┴┘" +); + +test_table!( + table_2x2_empty_height_list_together_with_width_list_dont_work_0, + { + Builder::from_iter(vec![vec!["", ""], vec!["", ""]]).build() + .with(Style::modern()) + .with(Padding::zero()) + .with(Height::list([1, 0])) + .with(Width::list([1, 0])) + }, + "┌─┬┐" + "│ ││" + "├─┼┤" + "│ ││" + "└─┴┘" +); + +test_table!( + table_2x2_empty_height_list_together_with_width_list_dont_work_1, + { + Builder::from_iter(vec![vec!["", ""], vec!["", ""]]).build() + .with(Style::modern()) + .with(Padding::zero()) + .with(Width::list([1, 0])) + .with(Height::list([1, 0])) + }, + "┌┬┐" + "│││" + "├┼┤" + "└┴┘" +); diff --git a/vendor/tabled/tests/derive/derive_test.rs b/vendor/tabled/tests/derive/derive_test.rs new file mode 100644 index 000000000..d27e43eee --- /dev/null +++ b/vendor/tabled/tests/derive/derive_test.rs @@ -0,0 +1,1090 @@ +#![cfg(feature = "derive")] +#![cfg(feature = "std")] + +use tabled::Tabled; + +// https://users.rust-lang.org/t/create-a-struct-from-macro-rules/19829 +macro_rules! test_tuple { + ( + $test_name:ident, + t: $(#[$struct_attr:meta])* { $( $(#[$attr:meta])* $ty:ty)* }, + init: { $($init:expr)* }, + expected: $headers:expr, $fields:expr, + $(pre: { $($init_block:stmt)* })? + ) => { + #[test] + fn $test_name() { + $($($init_block)*)? + + #[derive(Tabled)] + struct TestType( + $( $(#[$attr])* $ty, )* + ); + + let value = TestType($($init,)*); + + let fields: Vec<&'static str> = $fields.to_vec(); + let headers: Vec<&'static str> = $headers.to_vec(); + + assert_eq!(value.fields(), fields); + assert_eq!(TestType::headers(), headers); + assert_eq!(<TestType as Tabled>::LENGTH, headers.len()); + assert_eq!(<TestType as Tabled>::LENGTH, fields.len()); + } + }; +} + +macro_rules! test_enum { + ( + $test_name:ident, + t: $(#[$struct_attr:meta])* { $( $(#[$var_attr:meta])* $var:ident $({ $( $(#[$attr:meta])* $field:ident: $ty:ty),* $(,)? })? $(( $( $(#[$attr2:meta])* $ty2:ty),* $(,)? ))? )* }, + $(pre: { $($init_block:stmt)* })? + headers: $headers:expr, + tests: $($init:expr => $expected:expr,)* + ) => { + #[allow(dead_code, unused_imports)] + #[test] + fn $test_name() { + $($($init_block)*)? + + #[derive(Tabled)] + $(#[$struct_attr])* + enum TestType { + $( + $(#[$var_attr])* + $var $({ + $( $(#[$attr])* $field: $ty,)* + })? + + $(( + $( $(#[$attr2])* $ty2,)* + ))? + ),* + } + + let headers: Vec<&'static str> = $headers.to_vec(); + assert_eq!(TestType::headers(), headers); + assert_eq!(<TestType as Tabled>::LENGTH, headers.len()); + + { + use TestType::*; + $( + let variant = $init; + let fields: Vec<&'static str> = $expected.to_vec(); + assert_eq!(variant.fields(), fields); + )* + } + } + }; +} + +macro_rules! test_struct { + ( + $test_name:ident, + t: $(#[$struct_attr:meta])* { $( $(#[$attr:meta])* $field:ident: $ty:ty),* $(,)?} + $(pre: { $($init_block:stmt)* })? + init: { $( $val_field:ident: $val:expr),* $(,)?} + expected: $headers:expr, $fields:expr $(,)? + ) => { + + #[allow(dead_code, unused_imports)] + #[test] + fn $test_name() { + $($($init_block)*)? + + #[derive(Tabled)] + $(#[$struct_attr])* + struct TestType { + $( + $(#[$attr])* + $field: $ty, + )* + } + + let value = TestType { + $($val_field: $val,)* + }; + + let fields: Vec<&'static str> = $fields.to_vec(); + let headers: Vec<&'static str> = $headers.to_vec(); + assert_eq!(TestType::headers(), headers); + assert_eq!(value.fields(), fields); + assert_eq!(<TestType as Tabled>::LENGTH, headers.len()); + assert_eq!(<TestType as Tabled>::LENGTH, fields.len()); + } + }; +} + +#[allow(non_camel_case_types)] +type sstr = &'static str; + +mod tuple { + use super::*; + + test_tuple!(basic, t: { u8 sstr }, init: { 0 "v2" }, expected: ["0", "1"], ["0", "v2"],); + test_tuple!(empty, t: { }, init: { }, expected: [], [],); + + test_tuple!(rename, t: { u8 #[tabled(rename = "field 2")] sstr }, init: { 0 "123" }, expected: ["0", "field 2"], ["0", "123"],); + + test_tuple!(skip_0, t: { #[tabled(skip)] u8 #[tabled(rename = "field 2", skip)] sstr sstr }, init: { 0 "v2" "123" }, expected: ["2"], ["123"],); + test_tuple!(skip_1, t: { #[tabled(skip)] u8 #[tabled(skip)] #[tabled(rename = "field 2")] sstr sstr }, init: { 0 "v2" "123" }, expected: ["2"], ["123"],); + + test_tuple!(order_0, t: { #[tabled(order = 0)] u8 u8 u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); + test_tuple!(order_1, t: { #[tabled(order = 1)] u8 u8 u8}, init: { 0 1 2 }, expected: ["1", "0", "2"], ["1", "0", "2"],); + test_tuple!(order_2, t: { #[tabled(order = 2)] u8 u8 u8}, init: { 0 1 2 }, expected: ["1", "2", "0"], ["1", "2", "0"],); + test_tuple!(order_3, t: { u8 #[tabled(order = 0)] u8 u8}, init: { 0 1 2 }, expected: ["1", "0", "2"], ["1", "0", "2"],); + test_tuple!(order_4, t: { u8 #[tabled(order = 1)] u8 u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); + test_tuple!(order_5, t: { u8 #[tabled(order = 2)] u8 u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); + test_tuple!(order_6, t: { u8 u8 #[tabled(order = 0)] u8}, init: { 0 1 2 }, expected: ["2", "0", "1"], ["2", "0", "1"],); + test_tuple!(order_7, t: { u8 u8 #[tabled(order = 1)] u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); + test_tuple!(order_8, t: { u8 u8 #[tabled(order = 2)] u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); + test_tuple!(order_9, t: { #[tabled(order = 2)] u8 u8 #[tabled(order = 0)] u8}, init: { 0 1 2 }, expected: ["2", "1", "0"], ["2", "1", "0"],); + test_tuple!(order_10, t: { #[tabled(order = 2)] u8 #[tabled(order = 1)] u8 u8}, init: { 0 1 2 }, expected: ["2", "1", "0"], ["2", "1", "0"],); + test_tuple!(order_11, t: { #[tabled(order = 2)] u8 #[tabled(order = 2)] u8 #[tabled(order = 1)] u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); + test_tuple!(order_12, t: { #[tabled(order = 2)] u8 #[tabled(order = 2)] u8 #[tabled(order = 2)] u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); + test_tuple!(order_13, t: { #[tabled(order = 1)] u8 #[tabled(order = 1)] u8 #[tabled(order = 1)] u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); + test_tuple!(order_14, t: { #[tabled(order = 2)] u8 #[tabled(order = 1)] u8 #[tabled(order = 0)] u8}, init: { 0 1 2 }, expected: ["2", "1", "0"], ["2", "1", "0"],); + + test_tuple!(rename_all, t: #[tabled(rename_all = "UPPERCASE")] { u8 sstr}, init: { 0 "123" }, expected: ["0", "1"], ["0", "123"],); + test_tuple!(rename_all_field, t: { u8 #[tabled(rename_all = "UPPERCASE")] sstr}, init: { 0 "123" }, expected: ["0", "1"], ["0", "123"],); + test_tuple!(rename_all_field_with_rename_0, t: { u8 #[tabled(rename_all = "UPPERCASE", rename = "Something")] sstr}, init: { 0 "123" }, expected: ["0", "Something"], ["0", "123"],); + test_tuple!(rename_all_field_with_rename_1, t: { u8 #[tabled(rename = "Something")] #[tabled(rename_all = "UPPERCASE")] sstr}, init: { 0 "123" }, expected: ["0", "Something"], ["0", "123"],); + + test_tuple!( + display_option, + t: { u8 #[tabled(display_with = "display_option")] Option<sstr> }, + init: { 0 Some("v2") }, + expected: ["0", "1"], ["0", "some v2"], + pre: { + fn display_option(o: &Option<sstr>) -> String { + match o { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + ); + + test_tuple!( + display_option_args, + t: { u8 #[tabled(display_with("display_option", 1, "234"))] Option<sstr> }, + init: { 0 Some("v2") }, + expected: ["0", "1"], ["0", "some 1 234"], + pre: { + fn display_option(val: usize, text: &str) -> String { + format!("some {val} {text}") + } + } + ); + + test_tuple!( + display_option_self, + t: { u8 #[tabled(display_with = "Self::display_option")] Option<sstr> }, + init: { 0 Some("v2") }, + expected: ["0", "1"], ["0", "some v2"], + pre: { + impl TestType { + fn display_option(o: &Option<sstr>) -> String { + match o { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + } + ); + + test_tuple!( + display_option_self_2, + t: { u8 #[tabled(display_with("Self::display_option", self))] Option<sstr> }, + init: { 0 Some("v2") }, + expected: ["0", "1"], ["0", "some v2"], + pre: { + impl TestType { + fn display_option(o: &TestType) -> String { + match o.1 { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + } + ); + + test_tuple!( + display_option_self_3, + t: { u8 #[tabled(display_with("display_option", self))] Option<sstr> }, + init: { 0 Some("v2") }, + expected: ["0", "1"], ["0", "some v2"], + pre: { + fn display_option(o: &TestType) -> String { + match o.1 { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + ); + + // #[test] + // fn order_compile_fail_when_order_is_bigger_then_count_fields() { + // #[derive(Tabled)] + // struct St(#[tabled(order = 3)] u8, u8, u8); + // } +} + +mod enum_ { + use super::*; + + test_enum!( + basic, + t: { Security Embedded Frontend Unknown }, + headers: ["Security", "Embedded", "Frontend", "Unknown"], + tests: + Security => ["+", "", "", ""], + Embedded => ["", "+", "", ""], + Frontend => ["", "", "+", ""], + Unknown => ["", "", "", "+"], + ); + + test_enum!( + diverse, + t: { A { a: u8, b: i32 } B(sstr) K }, + headers: ["A", "B", "K"], + tests: + A { a: 1, b: 2 } => ["+", "", ""], + B("") => ["", "+", ""], + K => ["", "", "+"], + ); + + test_enum!( + rename_variant, + t: { #[tabled(rename = "Variant 1")] A { a: u8, b: i32 } #[tabled(rename = "Variant 2")] B(sstr) K }, + headers: ["Variant 1", "Variant 2", "K"], + tests: + A { a: 1, b: 2 } => ["+", "", ""], + B("") => ["", "+", ""], + K => ["", "", "+"], + ); + + test_enum!( + skip_variant, + t: { A { a: u8, b: i32 } #[tabled(skip)] B(sstr) K }, + headers: ["A", "K"], + tests: + A { a: 1, b: 2 } => ["+", ""], + B("") => ["", ""], + K => ["", "+"], + ); + + test_enum!( + inline_variant, + t: { + #[tabled(inline("Auto::"))] Auto { #[tabled(rename = "mod")] model: sstr, engine: sstr } + #[tabled(inline)] Bikecycle( #[tabled(rename = "name")] sstr, #[tabled(inline)] Bike ) + Skateboard + }, + pre: { + #[derive(Tabled)] + struct Bike { brand: sstr, price: f32 } + } + headers: ["Auto::mod", "Auto::engine", "name", "brand", "price", "Skateboard"], + tests: + Skateboard => ["", "", "", "", "", "+"], + Auto { model: "Mini", engine: "v8" } => ["Mini", "v8", "", "", "", ""], + Bikecycle("A bike", Bike { brand: "Canyon", price: 2000.0 })=> ["", "", "A bike", "Canyon", "2000", ""], + ); + + test_enum!( + inline_field_with_display_function, + t: { + #[tabled(inline("backend::"))] + Backend { #[tabled(display_with = "display", rename = "name")] value: sstr } + Frontend + }, + pre: { + fn display(_: sstr) -> String { + "asd".to_string() + } + } + headers: ["backend::name", "Frontend"], + tests: + Backend { value: "123" } => ["asd", ""], + Frontend => ["", "+"], + ); + + test_enum!( + inline_field_with_display_self_function, + t: { + #[tabled(inline("backend::"))] + Backend { #[tabled()] #[tabled(display_with("display", self), rename = "name")] value: sstr } + Frontend + }, + pre: { + fn display<T>(_: &T) -> String { + "asd".to_string() + } + } + headers: ["backend::name", "Frontend"], + tests: + Backend { value: "123" } => ["asd", ""], + Frontend => ["", "+"], + ); + + test_enum!( + with_display, + t: { + #[tabled(inline)] + A(#[tabled(display_with = "format::<4>")] sstr) + B + }, + pre: { + fn format<const ID: usize>(_: sstr) -> String { + ID.to_string() + } + } + headers: ["0", "B"], + tests: + A("") => ["4", ""], + B => ["", "+"], + ); + + test_enum!( + with_display_self, + t: { + #[tabled(inline)] + A(#[tabled(display_with("Self::format::<4>", self))] sstr) + B + }, + pre: { + impl TestType { + fn format<const ID: usize>(&self) -> String { + ID.to_string() + } + } + } + headers: ["0", "B"], + tests: + A("") => ["4", ""], + B => ["", "+"], + ); + + test_enum!( + rename_all_variant, + t: { + #[tabled(rename_all = "snake_case")] + VariantName1 { a: u8, b: i32 } + #[tabled(rename_all = "UPPERCASE")] + VariantName2(String) + K + }, + headers: ["variant_name1", "VARIANTNAME2", "K"], + tests: + ); + + test_enum!( + rename_all_enum, + t: #[tabled(rename_all = "snake_case")] { + VariantName1 { a: u8, b: i32 } + VariantName2(String) + K + }, + headers: ["variant_name1", "variant_name2", "k"], + tests: + ); + + test_enum!( + rename_all_enum_inherited_inside_struct_enum, + t: #[tabled(rename_all = "snake_case")] { + #[tabled(inline)] + VariantName1 { some_field_1: u8, some_field_2: i32 } + VariantName2(String) + K + }, + headers: ["some_field_1", "some_field_2", "variant_name2", "k"], + tests: + ); + + test_enum!( + rename_all_enum_inherited_inside_struct_override_by_rename_enum, + t: #[tabled(rename_all = "snake_case")] { + #[tabled(inline)] + VariantName1 { + #[tabled(rename = "f1")] + some_field_1: u8, + #[tabled(rename = "f2")] + some_field_2: i32, + } + VariantName2(String) + K + }, + headers: ["f1", "f2", "variant_name2", "k"], + tests: + ); + + test_enum!( + rename_all_enum_inherited_inside_struct_override_by_rename_all_enum, + t: #[tabled(rename_all = "snake_case")] { + #[tabled(inline)] + VariantName1 { + #[tabled(rename_all = "UPPERCASE")] + some_field_1: u8, + #[tabled(rename_all = "CamelCase")] + some_field_2: i32, + } + VariantName2(String) + K + }, + headers: ["SOMEFIELD1", "someField2", "variant_name2", "k"], + tests: + ); + + test_enum!( + rename_all_variant_inherited_inside_struct_enum, + t: #[tabled(rename_all = "snake_case")] { + #[tabled(inline)] + #[tabled(rename_all = "snake_case")] + VariantName1 { + some_field_1: u8, + some_field_2: i32, + } + VariantName2(String) + K + }, + headers: ["some_field_1", "some_field_2", "variant_name2", "k"], + tests: + ); + + test_enum!( + rename_all_variant_inherited_inside_struct_enum_overridden_by_rename, + t: #[tabled(rename_all = "snake_case")] { + #[tabled(inline, rename_all = "snake_case")] + VariantName1 { + #[tabled(rename = "f1")] + some_field_1: u8, + #[tabled(rename = "f2")] + some_field_2: i32, + } + VariantName2(String) + K + }, + headers: ["f1", "f2", "variant_name2", "k"], + tests: + ); + + test_enum!( + rename_all_variant_inherited_inside_struct_override_by_rename_all_enum, + t: #[tabled(rename_all = "snake_case")] { + #[tabled(rename_all = "snake_case", inline)] + VariantName1 { + #[tabled(rename_all = "UPPERCASE")] + some_field_1: u8, + #[tabled(rename_all = "CamelCase")] + some_field_2: i32, + } + VariantName2(String) + K + }, + headers: ["SOMEFIELD1", "someField2", "variant_name2", "k"], + tests: + ); + + test_enum!( + inline_enum_as_whole, + t: #[tabled(inline)] { + AbsdEgh { a: u8, b: i32 } + B(String) + K + }, + headers: ["TestType"], + tests: + AbsdEgh { a: 0, b: 0 } => ["AbsdEgh"], + B(String::new()) => ["B"], + K => ["K"], + ); + + test_enum!( + inline_enum_as_whole_and_rename, + t: + #[tabled(inline, rename_all = "snake_case")] + { + AbsdEgh { a: u8, b: i32 } + B(String) + K + }, + headers: ["TestType"], + tests: + AbsdEgh { a: 0, b: 0 } => ["absd_egh"], + B(String::new()) => ["b"], + K => ["k"], + ); + + test_enum!( + inline_enum_as_whole_and_rename_inner, + t: #[tabled(inline)] { + #[tabled(rename_all = "snake_case")] + AbsdEgh { a: u8, b: i32 } + #[tabled(rename_all = "lowercase")] + B(String) + K + }, + headers: ["TestType"], + tests: + AbsdEgh { a: 0, b: 0 } => ["absd_egh"], + B(String::new()) => ["b"], + K => ["K"], + ); + + test_enum!( + inline_enum_name, + t: #[tabled(inline("A struct name"))] { + AbsdEgh { a: u8, b: i32 } + B(String) + K + }, + headers: ["A struct name"], + tests: + AbsdEgh { a: 0, b: 0 } => ["AbsdEgh"], + B(String::new()) => ["B"], + K => ["K"], + ); + + test_enum!( + enum_display_with_variant, + t: { + #[tabled(display_with = "display_variant1")] + AbsdEgh { a: u8, b: i32 } + #[tabled(display_with = "display_variant2::<200>")] + B(String) + #[tabled(display_with = "some::bar::display_variant1")] + K + }, + pre: { + fn display_variant1() -> &'static str { + "Hello World" + } + + fn display_variant2<const VAL: usize>() -> String { + format!("asd {VAL}") + } + + pub mod some { + pub mod bar { + pub fn display_variant1() -> &'static str { + "Hello World 123" + } + } + } + } + headers: ["AbsdEgh", "B", "K"], + tests: + AbsdEgh { a: 0, b: 0 } => ["Hello World", "", ""], + B(String::new()) => ["", "asd 200", ""], + K => ["", "", "Hello World 123"], + ); + + test_enum!( + enum_display_with_self_variant, + t: { + #[tabled(display_with("display_variant1", self))] + AbsdEgh { a: u8, b: i32 } + #[tabled(display_with("display_variant2::<200, _>", self))] + B(String) + #[tabled(display_with("some::bar::display_variant1", self))] + K + }, + pre: { + fn display_variant1<D>(_: &D) -> &'static str { + "Hello World" + } + + fn display_variant2<const VAL: usize, D>(_: &D) -> String { + format!("asd {VAL}") + } + + pub mod some { + pub mod bar { + pub fn display_variant1<D>(_: &D) -> &'static str { + "Hello World 123" + } + } + } + } + headers: ["AbsdEgh", "B", "K"], + tests: + AbsdEgh { a: 0, b: 0 } => ["Hello World", "", ""], + B(String::new()) => ["", "asd 200", ""], + K => ["", "", "Hello World 123"], + ); + + test_enum!( + enum_display_with_arguments, + t: { + #[tabled(display_with("display1", 1, 2, self))] + AbsdEgh { a: u8, b: i32 } + #[tabled(display_with("display2::<200>", "Hello World"))] + B(String) + #[tabled(display_with("display1", 100, 200, self))] + K + }, + pre: { + fn display1<D>(val: usize, val2: usize, _: &D) -> String { + format!("{val} {val2}") + } + + fn display2<const VAL: usize>(val: &str) -> String { + format!("asd {VAL} {val}") + } + } + headers: ["AbsdEgh", "B", "K"], + tests: + AbsdEgh { a: 0, b: 0 } => ["1 2", "", ""], + B(String::new()) => ["", "asd 200 Hello World", ""], + K => ["", "", "100 200"], + ); + + test_enum!(order_0, t: { #[tabled(order = 0)] V1(u8) V2(u8) V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); + test_enum!(order_1, t: { #[tabled(order = 1)] V1(u8) V2(u8) V3(u8) }, headers: ["V2", "V1", "V3"], tests: V1(0) => ["", "+", ""], V2(0) => ["+", "", ""], V3(0) => ["", "", "+"],); + test_enum!(order_2, t: { #[tabled(order = 2)] V1(u8) V2(u8) V3(u8) }, headers: ["V2", "V3", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["+", "", ""], V3(0) => ["", "+", ""],); + test_enum!(order_3, t: { V1(u8) #[tabled(order = 0)] V2(u8) V3(u8) }, headers: ["V2", "V1", "V3"], tests: V1(0) => ["", "+", ""], V2(0) => ["+", "", ""], V3(0) => ["", "", "+"],); + test_enum!(order_4, t: { V1(u8) #[tabled(order = 1)] V2(u8) V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); + test_enum!(order_5, t: { V1(u8) #[tabled(order = 2)] V2(u8) V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); + test_enum!(order_6, t: { V1(u8) V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V1", "V2"], tests: V1(0) => ["", "+", ""], V2(0) => ["", "", "+"], V3(0) => ["+", "", ""],); + test_enum!(order_7, t: { V1(u8) V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); + test_enum!(order_8, t: { V1(u8) V2(u8) #[tabled(order = 2)] V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); + test_enum!(order_9, t: { #[tabled(order = 2)] V1(u8) V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V2", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["", "+", ""], V3(0) => ["+", "", ""],); + test_enum!(order_10, t: { #[tabled(order = 2)] V1(u8) V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V2", "V3", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["+", "", ""], V3(0) => ["", "+", ""],); + test_enum!(order_11, t: { #[tabled(order = 2)] V1(u8) #[tabled(order = 2)] V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); + test_enum!(order_12, t: { #[tabled(order = 2)] V1(u8) #[tabled(order = 1)] V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V2", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["", "+", ""], V3(0) => ["+", "", ""],); + test_enum!(order_13, t: { #[tabled(order = 0)] V1(u8) #[tabled(order = 0)] V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V1", "V2"], tests: V1(0) => ["", "+", ""], V2(0) => ["", "", "+"], V3(0) => ["+", "", ""],); + test_enum!(order_14, t: { #[tabled(order = 1)] V1(u8) #[tabled(order = 1)] V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); + test_enum!(order_15, t: { #[tabled(order = 2)] V1(u8) #[tabled(order = 2)] V2(u8) #[tabled(order = 2)] V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); + + test_enum!(order_0_inlined, t: #[tabled(inline)] { #[tabled(order = 1)] V1(u8) V2(u8) V3(u8) }, headers: ["TestType"], tests: V1(0) => ["V1"], V2(0) => ["V2"], V3(0) => ["V3"],); +} + +mod unit { + use super::*; + + #[test] + fn basic() { + #[derive(Tabled)] + struct St; + let st = St; + + assert!(st.fields().is_empty()); + assert!(St::headers().is_empty()); + assert_eq!(St::LENGTH, 0); + } +} + +mod structure { + use super::*; + + test_struct!(empty, t: { } init: { } expected: [], []); + test_struct!(general, t: { f1: u8, f2: sstr } init: { f1: 0, f2: "v2" } expected: ["f1", "f2"], ["0", "v2"]); + test_struct!(rename, t: { #[tabled(rename = "field 1")] f1: u8, #[tabled(rename = "field 2")] f2: sstr } init: { f1: 0, f2: "v2" } expected: ["field 1", "field 2"], ["0", "v2"]); + test_struct!(skip, t: { #[tabled(skip)] f1: u8, #[tabled(rename = "field 2", skip)] f2: sstr, f3: sstr } init: { f1: 0, f2: "v2", f3: "123" } expected: ["f3"], ["123"]); + test_struct!(skip_true, t: { #[tabled(skip = true)] f1: u8, #[tabled(rename = "field 2", skip = true)] f2: sstr, f3: sstr } init: { f1: 0, f2: "v2", f3: "123" } expected: ["f3"], ["123"]); + test_struct!( + inline, + t: { + #[tabled(inline = true)] + id: u8, + name: sstr, + #[tabled(inline)] + ed: Education + } + pre: { + #[derive(Tabled)] + struct Education { uni: sstr, graduated: bool } + } + init: { id: 0, name: "Maxim", ed: Education { uni: "BNTU", graduated: true }} + expected: ["u8", "name","uni","graduated"], ["0", "Maxim", "BNTU", "true"] + ); + test_struct!( + inline_with_prefix, + t: { + #[tabled(rename = "it's an ignored option", inline)] + id: u8, + name: sstr, + #[tabled(inline("education::"))] + ed: Education, + } + pre: { + #[derive(Tabled)] + struct Education { uni: sstr, graduated: bool } + } + init: { id: 0, name: "Maxim", ed: Education { uni: "BNTU", graduated: true }} + expected: ["u8", "name","education::uni","education::graduated"], ["0", "Maxim", "BNTU", "true"] + ); + test_struct!( + display_with, + t: { + f1: u8, + #[tabled(display_with = "display_option")] + f2: Option<sstr>, + } + pre: { + fn display_option(o: &Option<sstr>) -> String { + match o { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + init: { f1: 0, f2: Some("v2") } + expected: ["f1", "f2"], ["0", "some v2"] + ); + test_struct!( + display_with_args, + t: { + f1: u8, + #[tabled(display_with("display_option", 1, 2, 3))] + f2: Option<sstr>, + } + pre: { + fn display_option(v1: usize, v2: usize, v3: usize) -> String { + format!("{v1} {v2} {v3}") + } + } + init: { f1: 0, f2: Some("v2") } + expected: ["f1", "f2"], ["0", "1 2 3"] + ); + test_struct!( + display_with_self_static_method, + t: { + f1: u8, + #[tabled(display_with = "Self::display_option")] + f2: Option<sstr>, + } + pre: { + impl TestType { + fn display_option(o: &Option<sstr>) -> String { + match o { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + } + init: { f1: 0, f2: Some("v2") } + expected: ["f1", "f2"], ["0", "some v2"] + ); + test_struct!( + display_with_self_static_method_2, + t: { + f1: u8, + #[tabled(display_with("Self::display_option", self))] + f2: Option<sstr>, + } + pre: { + impl TestType { + fn display_option(o: &TestType) -> String { + match o.f2 { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + } + init: { f1: 0, f2: Some("v2") } + expected: ["f1", "f2"], ["0", "some v2"] + ); + test_struct!( + display_with_self_2_self_static_method_2, + t: { + f1: u8, + #[tabled(display_with("Self::display_option", self))] + f2: Option<sstr>, + } + pre: { + impl TestType { + fn display_option(&self) -> String { + match self.f2 { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + } + init: { f1: 0, f2: Some("v2") } + expected: ["f1", "f2"], ["0", "some v2"] + ); + test_struct!( + display_with_self_2_self_static_method, + t: { + f1: u8, + #[tabled(display_with("display_option", self))] + f2: Option<sstr>, + } + pre: { + fn display_option(o: &TestType) -> String { + match o.f2 { + Some(s) => format!("some {s}"), + None => "none".to_string(), + } + } + } + init: { f1: 0, f2: Some("v2") } + expected: ["f1", "f2"], ["0", "some v2"] + ); + test_struct!(order_0, t: { #[tabled(order = 0)] f0: u8, f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f1", "f2"], ["0", "1", "2"]); + test_struct!(order_1, t: { #[tabled(order = 1)] f0: u8, f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f1", "f0", "f2"], ["1", "0", "2"]); + test_struct!(order_2, t: { #[tabled(order = 2)] f0: u8, f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f1", "f2", "f0"], ["1", "2", "0"]); + test_struct!(order_3, t: { f0: u8, #[tabled(order = 0)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f1", "f0", "f2"], ["1", "0", "2"]); + test_struct!(order_4, t: { f0: u8, #[tabled(order = 1)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f1", "f2"], ["0", "1", "2"]); + test_struct!(order_5, t: { f0: u8, #[tabled(order = 2)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f2", "f1"], ["0", "2", "1"]); + test_struct!(order_6, t: { f0: u8, f1: u8, #[tabled(order = 0)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f0", "f1"], ["2", "0", "1"]); + test_struct!(order_7, t: { f0: u8, f1: u8, #[tabled(order = 1)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f2", "f1"], ["0", "2", "1"]); + test_struct!(order_8, t: { f0: u8, f1: u8, #[tabled(order = 2)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f1", "f2"], ["0", "1", "2"]); + test_struct!(order_9, t: { #[tabled(order = 2)] f0: u8, f1: u8, #[tabled(order = 0)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f1", "f0"], ["2", "1", "0"]); + test_struct!(order_10, t: { #[tabled(order = 2)] f0: u8, #[tabled(order = 1)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f1", "f0"], ["2", "1", "0"]); + test_struct!(order_11, t: { #[tabled(order = 2)] f0: u8, #[tabled(order = 2)] f1: u8, #[tabled(order = 1)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f2", "f1"], ["0", "2", "1"]); + test_struct!(order_12, t: { #[tabled(order = 2)] f0: u8, #[tabled(order = 1)] f1: u8, #[tabled(order = 0)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f1", "f0"], ["2", "1", "0"]); + + test_struct!( + rename_all, + t: #[tabled(rename_all = "UPPERCASE")] { f1: u8, f2: sstr } + init: { f1: 0, f2: "v2" } + expected: ["F1", "F2"], ["0", "v2"] + ); + test_struct!( + rename_all_override_in_field_by_rename, + t: #[tabled(rename_all = "UPPERCASE")] { #[tabled(rename = "213213")] f1: u8, f2: sstr } + init: { f1: 0, f2: "v2" } + expected: ["213213", "F2"], ["0", "v2"] + ); + test_struct!( + rename_all_override_in_field_by_rename_all, + t: #[tabled(rename_all = "UPPERCASE")] { #[tabled(rename_all = "lowercase")] f1: u8, f2: sstr } + init: { f1: 0, f2: "v2" } + expected: ["f1", "F2"], ["0", "v2"] + ); + test_struct!( + rename_all_field, + t: { #[tabled(rename_all = "lowercase")] f1: u8, #[tabled(rename_all = "UPPERCASE")] f2: sstr } + init: { f1: 0, f2: "v2" } + expected: ["f1", "F2"], ["0", "v2"] + ); + test_struct!( + rename_all_field_overridden_by_rename, + t: { #[tabled(rename_all = "lowercase", rename = "Hello")] f1: u8, #[tabled(rename_all = "UPPERCASE")] f2: sstr } + init: { f1: 0, f2: "v2" } + expected: ["Hello", "F2"], ["0", "v2"] + ); + + // #[test] + // fn order_compile_fail_when_order_is_bigger_then_count_fields() { + // #[derive(Tabled)] + // struct St { + // #[tabled(order = 3)] + // f0: u8, + // f1: u8, + // f2: u8, + // } + // } +} + +test_tuple!(skipped_fields_not_implement_display_tuple, t: { #[tabled(skip)] () sstr }, init: { () "123" }, expected: ["1"], ["123"],); +test_struct!(skipped_fields_not_implement_display_struct, t: { #[tabled(skip)] _unit: (), s: sstr } init: { _unit: (), s: "123" } expected: ["s"], ["123"],); +test_struct!( + skipped_fields_not_implement_display_struct_in_inline, + t: { s: sstr, #[tabled(inline)] f: S1 } + pre: { + #[derive(Tabled)] + struct S1 { + #[tabled(skip)] + _unit: (), + s: sstr, + } + } + init: { s: "123", f: S1 { _unit: (), s: "..." } } + expected: ["s", "s"], ["123", "..."], +); +test_enum!( + skipped_fields_not_implement_display_enum, + t: { + #[tabled(inline("A::"))] + A { + name: sstr + } + #[tabled(inline("B::"))] + B { + issue: usize, + name: sstr, + #[tabled(skip)] + _gem: (), + } + #[tabled(inline("C::"))] + C(usize, #[tabled(skip)] (), sstr) + D + }, + headers: ["A::name", "B::issue", "B::name", "C::0", "C::2", "D"], + tests: + A { name: "nrdxp" } => ["nrdxp", "", "", "", "", ""], + B { _gem: (), issue: 32, name: "nrdxp" } => ["", "32", "nrdxp", "", "", ""], + C(32, (), "nrdxp") => ["", "", "", "32", "nrdxp", ""], + D => ["", "", "", "", "", "+"], +); + +test_struct!( + ignore_display_with_when_used_with_inline, + t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", inline)] f3: usize } + init: { f1: "123", f2: "456", f3: 789 } + expected: ["f1", "f2", "usize"], ["123", "456", "789"], +); +test_struct!( + ignore_display_with_when_used_with_inline_2, + t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", )] #[tabled(inline)] f3: usize } + init: { f1: "123", f2: "456", f3: 789 } + expected: ["f1", "f2", "usize"], ["123", "456", "789"], +); +test_struct!( + display_with_and_rename, + t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", rename = "asd")] f3: usize } + pre: { #[allow(dead_code)] fn print<T>(_: T) -> String { String::new() } } + init: { f1: "123", f2: "456", f3: 789 } + expected: ["f1", "f2", "asd"], ["123", "456", ""], +); +test_struct!( + display_with_and_rename_2, + t: { f1: sstr, f2: sstr, #[tabled(display_with = "print")] #[tabled(rename = "asd")] f3: usize } + pre: { #[allow(dead_code)] fn print<T>(_: T) -> String { String::new() } } + init: { f1: "123", f2: "456", f3: 789 } + expected: ["f1", "f2", "asd"], ["123", "456", ""], +); +test_struct!( + display_with_and_rename_all, + t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", rename_all = "UPPERCASE")] f3: usize } + pre: { #[allow(dead_code)] fn print<T>(_: T) -> String { String::new() } } + init: { f1: "123", f2: "456", f3: 789 } + expected: ["f1", "f2", "F3"], ["123", "456", ""], +); + +#[test] +fn rename_all_variants() { + macro_rules! test_case { + ( $name:ident, $case:expr ) => { + #[derive(Tabled)] + #[tabled(rename_all = $case)] + struct $name { + field: usize, + } + }; + } + + test_case!(S1, "UPPERCASE"); + test_case!(S2, "lowercase"); + test_case!(S3, "camelCase"); + test_case!(S4, "PascalCase"); + test_case!(S5, "snake_case"); + test_case!(S6, "SCREAMING_SNAKE_CASE"); + test_case!(S7, "kebab-case"); + test_case!(S8, "verbatimcase"); +} + +// #[test] +// fn wrong_rename_all_panic_when_used_as_not_first() { +// #[derive(Tabled)] +// #[tabled(rename_all = "UPPERCASE")] +// #[tabled(rename_all = "some wrong case")] +// struct Struct1 { +// field: usize, +// } + +// let st = Struct1 { field: 789 }; + +// assert_eq!(Struct1::headers(), vec!["FIELD"],); +// assert_eq!(st.fields(), vec!["789"]); + +// #[derive(Tabled)] +// #[tabled(rename_all = "UPPERCASE", rename_all = "some wrong case")] +// struct Struct2 { +// field: usize, +// } + +// let st = Struct2 { field: 789 }; + +// assert_eq!(Struct1::headers(), vec!["FIELD"],); +// assert_eq!(st.fields(), vec!["789"]); +// } + +#[test] +fn rename_all_gets_last_value() { + #[derive(Tabled)] + #[tabled(rename_all = "UPPERCASE")] + #[tabled(rename_all = "PascalCase")] + struct Struct1 { + field: usize, + } + + let st = Struct1 { field: 789 }; + + assert_eq!(Struct1::headers(), vec!["Field"],); + assert_eq!(st.fields(), vec!["789"]); + + #[derive(Tabled)] + #[tabled(rename_all = "UPPERCASE", rename_all = "PascalCase")] + struct Struct2 { + field: usize, + } + + let st = Struct2 { field: 789 }; + + assert_eq!(Struct1::headers(), vec!["Field"],); + assert_eq!(st.fields(), vec!["789"]); +} + +#[test] +fn test_order_skip_usage() { + #[derive(Tabled, Default)] + pub struct Example { + #[tabled(skip)] + #[allow(dead_code)] + id: usize, + name: String, + #[tabled(order = 0)] + details: String, + } + + #[derive(Tabled, Default)] + pub struct Example2 { + #[tabled(skip)] + #[allow(dead_code)] + id: usize, + name: String, + #[tabled(order = 1)] + details: String, + } + + assert_eq!(Example::headers(), vec!["details", "name"],); + assert_eq!(Example::default().fields(), vec!["", ""]); +} + +#[test] +fn test_skip_enum_0() { + #[allow(dead_code)] + #[derive(Tabled)] + enum Letters { + Vowels { + character: char, + lang: u8, + }, + Consonant(char), + #[tabled(skip)] + Digit, + } + + assert_eq!(Letters::headers(), vec!["Vowels", "Consonant"]); + assert_eq!(Letters::Consonant('c').fields(), vec!["", "+"]); + assert_eq!(Letters::Digit.fields(), vec!["", ""]); +} + +mod __ { + #[test] + fn dont_import_the_trait() { + #[derive(tabled::Tabled)] + struct __; + } +} diff --git a/vendor/tabled/tests/derive/mod.rs b/vendor/tabled/tests/derive/mod.rs new file mode 100644 index 000000000..d0d4b359f --- /dev/null +++ b/vendor/tabled/tests/derive/mod.rs @@ -0,0 +1 @@ +mod derive_test; diff --git a/vendor/tabled/tests/macros/col_row_test.rs b/vendor/tabled/tests/macros/col_row_test.rs new file mode 100644 index 000000000..e6d70edf0 --- /dev/null +++ b/vendor/tabled/tests/macros/col_row_test.rs @@ -0,0 +1,264 @@ +#![cfg(feature = "macros")] +#![cfg(feature = "std")] + +use tabled::{ + col, row, + settings::{format::Format, object::Segment, Alignment, Modify, Padding}, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + row_pair_test, + row!( + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + Matrix::new(4, 4) + .with(Modify::new(Segment::all()).with(Alignment::right())) + .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), + ), + "+--------------------------------------------------------+-------------------------------------------------------------+" + "| +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ |" + "| | [N] | [column 0] | [column 1] | [column 2] | | | | | | | | |" + "| +-------+--------------+--------------+--------------+ | | (N) | (column 0) | (column 1) | (column 2) | (column 3) | |" + "| | [0] | [0-0] | [0-1] | [0-2] | | | | | | | | |" + "| +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ |" + "| | [1] | [1-0] | [1-1] | [1-2] | | | | | | | | |" + "| +-------+--------------+--------------+--------------+ | | (0) | (0-0) | (0-1) | (0-2) | (0-3) | |" + "| | [2] | [2-0] | [2-1] | [2-2] | | | | | | | | |" + "| +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ |" + "| | | | | | | | |" + "| | | (1) | (1-0) | (1-1) | (1-2) | (1-3) | |" + "| | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ |" + "| | | | | | | | |" + "| | | (2) | (2-0) | (2-1) | (2-2) | (2-3) | |" + "| | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ |" + "| | | | | | | | |" + "| | | (3) | (3-0) | (3-1) | (3-2) | (3-3) | |" + "| | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ |" + "+--------------------------------------------------------+-------------------------------------------------------------+" +); + +test_table!( + col_pair_test, + col!( + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + Matrix::new(4, 4) + .with(Modify::new(Segment::all()).with(Alignment::right())) + .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), + ), + "+-------------------------------------------------------------+" + "| +-------+--------------+--------------+--------------+ |" + "| | [N] | [column 0] | [column 1] | [column 2] | |" + "| +-------+--------------+--------------+--------------+ |" + "| | [0] | [0-0] | [0-1] | [0-2] | |" + "| +-------+--------------+--------------+--------------+ |" + "| | [1] | [1-0] | [1-1] | [1-2] | |" + "| +-------+--------------+--------------+--------------+ |" + "| | [2] | [2-0] | [2-1] | [2-2] | |" + "| +-------+--------------+--------------+--------------+ |" + "+-------------------------------------------------------------+" + "| +-----+------------+------------+------------+------------+ |" + "| | | | | | | |" + "| | (N) | (column 0) | (column 1) | (column 2) | (column 3) | |" + "| | | | | | | |" + "| +-----+------------+------------+------------+------------+ |" + "| | | | | | | |" + "| | (0) | (0-0) | (0-1) | (0-2) | (0-3) | |" + "| | | | | | | |" + "| +-----+------------+------------+------------+------------+ |" + "| | | | | | | |" + "| | (1) | (1-0) | (1-1) | (1-2) | (1-3) | |" + "| | | | | | | |" + "| +-----+------------+------------+------------+------------+ |" + "| | | | | | | |" + "| | (2) | (2-0) | (2-1) | (2-2) | (2-3) | |" + "| | | | | | | |" + "| +-----+------------+------------+------------+------------+ |" + "| | | | | | | |" + "| | (3) | (3-0) | (3-1) | (3-2) | (3-3) | |" + "| | | | | | | |" + "| +-----+------------+------------+------------+------------+ |" + "+-------------------------------------------------------------+" +); + +test_table!( + row_duplication_test, + row!( + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))); + 3 + ), + "+--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+" + "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" + "| | [N] | [column 0] | [column 1] | [column 2] | | | [N] | [column 0] | [column 1] | [column 2] | | | [N] | [column 0] | [column 1] | [column 2] | |" + "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" + "| | [0] | [0-0] | [0-1] | [0-2] | | | [0] | [0-0] | [0-1] | [0-2] | | | [0] | [0-0] | [0-1] | [0-2] | |" + "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" + "| | [1] | [1-0] | [1-1] | [1-2] | | | [1] | [1-0] | [1-1] | [1-2] | | | [1] | [1-0] | [1-1] | [1-2] | |" + "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" + "| | [2] | [2-0] | [2-1] | [2-2] | | | [2] | [2-0] | [2-1] | [2-2] | | | [2] | [2-0] | [2-1] | [2-2] | |" + "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" + "+--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+" +); + +test_table!( + col_and_rows_test, + col!( + row!( + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + Matrix::new(4, 4) + .with(Modify::new(Segment::all()).with(Alignment::right())) + .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), + ), + Matrix::new(3, 5) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(2, 2, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + ), + "+--------------------------------------------------------------------------------------------------------------------------+" + "| +--------------------------------------------------------+-------------------------------------------------------------+ |" + "| | +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ | |" + "| | | [N] | [column 0] | [column 1] | [column 2] | | | | | | | | | |" + "| | +-------+--------------+--------------+--------------+ | | (N) | (column 0) | (column 1) | (column 2) | (column 3) | | |" + "| | | [0] | [0-0] | [0-1] | [0-2] | | | | | | | | | |" + "| | +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ | |" + "| | | [1] | [1-0] | [1-1] | [1-2] | | | | | | | | | |" + "| | +-------+--------------+--------------+--------------+ | | (0) | (0-0) | (0-1) | (0-2) | (0-3) | | |" + "| | | [2] | [2-0] | [2-1] | [2-2] | | | | | | | | | |" + "| | +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ | |" + "| | | | | | | | | | |" + "| | | | (1) | (1-0) | (1-1) | (1-2) | (1-3) | | |" + "| | | | | | | | | | |" + "| | | +-----+------------+------------+------------+------------+ | |" + "| | | | | | | | | | |" + "| | | | (2) | (2-0) | (2-1) | (2-2) | (2-3) | | |" + "| | | | | | | | | | |" + "| | | +-----+------------+------------+------------+------------+ | |" + "| | | | | | | | | | |" + "| | | | (3) | (3-0) | (3-1) | (3-2) | (3-3) | | |" + "| | | | | | | | | | |" + "| | | +-----+------------+------------+------------+------------+ | |" + "| +--------------------------------------------------------+-------------------------------------------------------------+ |" + "+--------------------------------------------------------------------------------------------------------------------------+" + "| +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | [N] | [column 0] | [column 1] | [column 2] | [column 3] | [column 4] | |" + "| +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | [0] | [0-0] | [0-1] | [0-2] | [0-3] | [0-4] | |" + "| +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | [1] | [1-0] | [1-1] | [1-2] | [1-3] | [1-4] | |" + "| +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | [2] | [2-0] | [2-1] | [2-2] | [2-3] | [2-4] | |" + "| +-------+--------------+--------------+--------------+--------------+--------------+ |" + "+--------------------------------------------------------------------------------------------------------------------------+" +); + +test_table!( + row_and_col_test, + row!( + col!( + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + Matrix::new(4, 4) + .with(Modify::new(Segment::all()).with(Alignment::right())) + .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), + ), + Matrix::new(3, 5) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(2, 2, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + ), + "+-----------------------------------------------------------------+--------------------------------------------------------------------------------------+" + "| +-------------------------------------------------------------+ | +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | +-------+--------------+--------------+--------------+ | | | [N] | [column 0] | [column 1] | [column 2] | [column 3] | [column 4] | |" + "| | | [N] | [column 0] | [column 1] | [column 2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | +-------+--------------+--------------+--------------+ | | | [0] | [0-0] | [0-1] | [0-2] | [0-3] | [0-4] | |" + "| | | [0] | [0-0] | [0-1] | [0-2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | +-------+--------------+--------------+--------------+ | | | [1] | [1-0] | [1-1] | [1-2] | [1-3] | [1-4] | |" + "| | | [1] | [1-0] | [1-1] | [1-2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | +-------+--------------+--------------+--------------+ | | | [2] | [2-0] | [2-1] | [2-2] | [2-3] | [2-4] | |" + "| | | [2] | [2-0] | [2-1] | [2-2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" + "| | +-------+--------------+--------------+--------------+ | | |" + "| +-------------------------------------------------------------+ | |" + "| | +-----+------------+------------+------------+------------+ | | |" + "| | | | | | | | | | |" + "| | | (N) | (column 0) | (column 1) | (column 2) | (column 3) | | | |" + "| | | | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ | | |" + "| | | | | | | | | | |" + "| | | (0) | (0-0) | (0-1) | (0-2) | (0-3) | | | |" + "| | | | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ | | |" + "| | | | | | | | | | |" + "| | | (1) | (1-0) | (1-1) | (1-2) | (1-3) | | | |" + "| | | | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ | | |" + "| | | | | | | | | | |" + "| | | (2) | (2-0) | (2-1) | (2-2) | (2-3) | | | |" + "| | | | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ | | |" + "| | | | | | | | | | |" + "| | | (3) | (3-0) | (3-1) | (3-2) | (3-3) | | | |" + "| | | | | | | | | | |" + "| | +-----+------------+------------+------------+------------+ | | |" + "| +-------------------------------------------------------------+ | |" + "+-----------------------------------------------------------------+--------------------------------------------------------------------------------------+" +); + +test_table!( + row_str_test, + row!("hello", "world"), + "+-------+-------+" + "| hello | world |" + "+-------+-------+" +); + +test_table!( + row_str_duplication_test, + row!("duplicate me"; 5), + "+--------------+--------------+--------------+--------------+--------------+" + "| duplicate me | duplicate me | duplicate me | duplicate me | duplicate me |" + "+--------------+--------------+--------------+--------------+--------------+" +); + +test_table!( + row_display_mixed_test, + row!("str", "string".to_string(), 55, false), + "+-----+--------+----+-------+" + "| str | string | 55 | false |" + "+-----+--------+----+-------+" +); + +test_table!( + col_display_mixed_test, + col!("str", "string".to_string(), 55, false), + "+--------+" + "| str |" + "+--------+" + "| string |" + "+--------+" + "| 55 |" + "+--------+" + "| false |" + "+--------+" +); diff --git a/vendor/tabled/tests/macros/mod.rs b/vendor/tabled/tests/macros/mod.rs new file mode 100644 index 000000000..be52241e9 --- /dev/null +++ b/vendor/tabled/tests/macros/mod.rs @@ -0,0 +1 @@ +mod col_row_test; diff --git a/vendor/tabled/tests/main.rs b/vendor/tabled/tests/main.rs new file mode 100644 index 000000000..35004b906 --- /dev/null +++ b/vendor/tabled/tests/main.rs @@ -0,0 +1,7 @@ +mod core; +mod derive; +mod macros; +mod settings; + +#[cfg(feature = "std")] +mod matrix; diff --git a/vendor/tabled/tests/matrix/matrix.rs b/vendor/tabled/tests/matrix/matrix.rs new file mode 100644 index 000000000..48ba55f9b --- /dev/null +++ b/vendor/tabled/tests/matrix/matrix.rs @@ -0,0 +1,161 @@ +use std::{ + fmt::{self, Display}, + iter::FromIterator, + string::ToString, +}; + +use tabled::{ + grid::config::ColoredConfig, + grid::dimension::CompleteDimensionVecRecords, + grid::records::vec_records::{CellInfo, VecRecords}, + settings::{object::Segment, Alignment, Modify, TableOption}, + Table, Tabled, +}; + +use super::matrix_list::MatrixList; + +/// A helper table factory. +/// +/// It uses center alignment by default, because it's more complex and may spot more issues. +#[derive(Debug, Clone)] +pub struct Matrix { + data: Vec<Vec<String>>, + size: (usize, usize), +} + +impl Matrix { + pub fn empty() -> Self { + Self { + data: vec![], + size: (0, 0), + } + } + + pub fn with_no_frame(rows: usize, columns: usize) -> Self { + Self { + data: create_matrix(rows, columns), + size: (rows, columns), + } + } + + pub fn new(rows: usize, columns: usize) -> Self { + Self::with_no_frame(rows, columns) + .with_header() + .with_index() + } + + pub fn vec(rows: usize, columns: usize) -> Vec<Vec<String>> { + Self::new(rows, columns).to_vec() + } + + pub fn table(rows: usize, columns: usize) -> Table { + Self::new(rows, columns).to_table() + } + + pub fn list<const ROWS: usize, const COLUMNS: usize>() -> Vec<MatrixList<COLUMNS, true>> { + create_list::<ROWS, COLUMNS>() + } + + pub fn iter<I, T>(iter: I) -> Table + where + I: IntoIterator<Item = T>, + T: Tabled, + { + let mut table = tabled::Table::new(iter); + table.with(Modify::new(Segment::all()).with(Alignment::center())); + table + } + + pub fn with_index(mut self) -> Self { + set_index(&mut self.data); + self + } + + pub fn with_header(mut self) -> Self { + set_header(&mut self.data, self.size.1); + self + } + + pub fn insert<V: ToString>(mut self, pos: tabled::grid::config::Position, value: V) -> Self { + self.data[pos.0][pos.1] = value.to_string(); + self + } + + pub fn to_table(&self) -> Table { + let mut table = tabled::Table::from_iter(self.data.clone()); + table.with(Modify::new(Segment::all()).with(Alignment::center())); + table + } + + pub fn to_vec(&self) -> Vec<Vec<String>> { + self.data.clone() + } + + pub fn with<O>(self, opt: O) -> Table + where + O: TableOption< + VecRecords<CellInfo<String>>, + CompleteDimensionVecRecords<'static>, + ColoredConfig, + >, + { + let mut table = self.to_table(); + table.with(opt); + table + } +} + +impl Display for Matrix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.clone().to_table().fmt(f) + } +} + +fn create_matrix(rows: usize, columns: usize) -> Vec<Vec<String>> { + let mut arr = Vec::with_capacity(rows); + for row in 0..rows { + let mut data = Vec::with_capacity(columns); + for column in 0..columns { + let text = format!("{row}-{column}"); + data.push(text); + } + + arr.push(data); + } + + arr +} + +fn set_header(data: &mut Vec<Vec<String>>, columns: usize) { + data.insert( + 0, + (0..columns) + .map(|n| format!("column {n}")) + .collect::<Vec<_>>(), + ); +} + +fn set_index(data: &mut [Vec<String>]) { + if data.is_empty() { + return; + } + + data[0].insert(0, "N".to_owned()); + + for (n, row) in data.iter_mut().skip(1).enumerate() { + row.insert(0, n.to_string()); + } +} + +fn create_list<const ROWS: usize, const COLUMNS: usize>() -> Vec<MatrixList<COLUMNS, true>> { + let mut arr = Vec::with_capacity(ROWS); + for row in 0..ROWS { + let data = (0..COLUMNS) + .map(|column| format!("{row}-{column}")) + .collect::<Vec<_>>(); + let list = MatrixList::with_index(row, data); + arr.push(list); + } + + arr +} diff --git a/vendor/tabled/tests/matrix/matrix_list.rs b/vendor/tabled/tests/matrix/matrix_list.rs new file mode 100644 index 000000000..9f58400ca --- /dev/null +++ b/vendor/tabled/tests/matrix/matrix_list.rs @@ -0,0 +1,58 @@ +use std::{ + borrow::Cow, + iter::once, + ops::{Index, IndexMut}, +}; + +use tabled::Tabled; + +#[derive(Debug)] +pub struct MatrixList<const N: usize, const INDEX: bool> { + data: Vec<String>, +} + +impl<const N: usize> MatrixList<N, false> { + #[allow(dead_code)] + pub fn new(data: Vec<String>) -> Self { + Self { data } + } +} + +impl<const N: usize> MatrixList<N, true> { + pub fn with_index(index: usize, mut data: Vec<String>) -> Self { + assert_eq!(data.len(), N); + data.insert(0, index.to_string()); + Self { data } + } +} + +impl<const N: usize, const INDEX: bool> Index<usize> for MatrixList<N, INDEX> { + type Output = String; + + fn index(&self, index: usize) -> &Self::Output { + &self.data[index] + } +} + +impl<const N: usize, const INDEX: bool> IndexMut<usize> for MatrixList<N, INDEX> { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.data[index] + } +} + +impl<const N: usize, const INDEX: bool> Tabled for MatrixList<N, INDEX> { + const LENGTH: usize = N + 1; + + fn fields(&self) -> Vec<Cow<'_, str>> { + self.data.iter().cloned().map(Cow::Owned).collect() + } + + fn headers() -> Vec<Cow<'static, str>> { + let header = (0..N).map(|n| format!("column {n}")); + + match INDEX { + true => once("N".to_owned()).chain(header).map(Cow::Owned).collect(), + false => header.map(Cow::Owned).collect(), + } + } +} diff --git a/vendor/tabled/tests/matrix/mod.rs b/vendor/tabled/tests/matrix/mod.rs new file mode 100644 index 000000000..d2d42cfc5 --- /dev/null +++ b/vendor/tabled/tests/matrix/mod.rs @@ -0,0 +1,6 @@ +#[allow(clippy::module_inception)] +mod matrix; +mod matrix_list; + +pub use matrix::Matrix; +pub use matrix_list::MatrixList; diff --git a/vendor/tabled/tests/settings/alignment_test.rs b/vendor/tabled/tests/settings/alignment_test.rs new file mode 100644 index 000000000..0a8145ae2 --- /dev/null +++ b/vendor/tabled/tests/settings/alignment_test.rs @@ -0,0 +1,138 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + locator::ByColumnName, + object::{Columns, Rows, Segment}, + Alignment, Modify, Padding, Style, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + full_alignment, + Matrix::new(3, 3).with(Style::psql()).with(Modify::new(Segment::all()).with(Alignment::left())), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + head_and_data_alignment, + Matrix::new(3, 3) + .with(Modify::new(Rows::first()).with(Alignment::left())) + .with(Modify::new(Rows::new(1..)).with(Alignment::right())), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + full_alignment_multiline, + Matrix::new(3, 3).insert((3, 2), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | https:// | 2-2 " + " | | www | " + " | | . | " + " | | redhat | " + " | | .com | " + " | | /en | " +); + +test_table!( + vertical_alignment_test, + Matrix::new(3, 3) + .insert((2, 2), "E\nnde\navou\nros") + .insert((3, 2), "Red\nHat") + .insert((3, 3), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new(Columns::new(1..)).with(Alignment::bottom())), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | | E | " + " | | nde | " + " | | avou | " + " | 1-0 | ros | 1-2 " + " 2 | | | https:// " + " | | | www " + " | | | . " + " | | | redhat " + " | | Red | .com " + " | 2-0 | Hat | /en " +); + +test_table!( + alignment_doesnt_change_padding, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Padding::new(3, 0, 0, 0))) + .with(Modify::new(Segment::all()).with(Alignment::left())), + " N| column 0| column 1| column 2" + "----+-----------+-----------+-----------" + " 0| 0-0 | 0-1 | 0-2 " + " 1| 1-0 | 1-1 | 1-2 " + " 2| 2-0 | 2-1 | 2-2 " +); + +test_table!( + alignment_global, + Matrix::new(3, 3).with(Style::psql()).with(Alignment::right()), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + padding_by_column_name, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(ByColumnName::new("column 0")).with(Padding::new(3, 3, 0, 0))) + .with(Modify::new(Segment::all()).with(Alignment::center())), + " N | column 0 | column 1 | column 2 " + "---+--------------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + padding_by_column_name_not_first_row, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(ByColumnName::new("0-2")).with(Padding::new(3, 3, 0, 0))) + .with(Modify::new(Segment::all()).with(Alignment::center())), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + padding_by_column_name_not_existing, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(ByColumnName::new("column 01123123")).with(Padding::new(3, 3, 0, 0))) + .with(Modify::new(Segment::all()).with(Alignment::center())), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); diff --git a/vendor/tabled/tests/settings/color_test.rs b/vendor/tabled/tests/settings/color_test.rs new file mode 100644 index 000000000..b81c63ae3 --- /dev/null +++ b/vendor/tabled/tests/settings/color_test.rs @@ -0,0 +1,34 @@ +#![cfg(feature = "std")] + +use tabled::settings::{Color, Modify}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + color_global, + Matrix::new(3, 3).with(Color::FG_MAGENTA), + "+---+----------+----------+----------+" + "| \u{1b}[35mN\u{1b}[39m | \u{1b}[35mcolumn 0\u{1b}[39m | \u{1b}[35mcolumn 1\u{1b}[39m | \u{1b}[35mcolumn 2\u{1b}[39m |" + "+---+----------+----------+----------+" + "| \u{1b}[35m0\u{1b}[39m | \u{1b}[35m0-0\u{1b}[39m | \u{1b}[35m0-1\u{1b}[39m | \u{1b}[35m0-2\u{1b}[39m |" + "+---+----------+----------+----------+" + "| \u{1b}[35m1\u{1b}[39m | \u{1b}[35m1-0\u{1b}[39m | \u{1b}[35m1-1\u{1b}[39m | \u{1b}[35m1-2\u{1b}[39m |" + "+---+----------+----------+----------+" + "| \u{1b}[35m2\u{1b}[39m | \u{1b}[35m2-0\u{1b}[39m | \u{1b}[35m2-1\u{1b}[39m | \u{1b}[35m2-2\u{1b}[39m |" + "+---+----------+----------+----------+" +); + +test_table!( + color_cell, + Matrix::new(3, 3).with(Modify::new((0, 0)).with(Color::BG_BRIGHT_BLACK)), + "+---+----------+----------+----------+" + "| \u{1b}[100mN\u{1b}[49m | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); diff --git a/vendor/tabled/tests/settings/colorization.rs b/vendor/tabled/tests/settings/colorization.rs new file mode 100644 index 000000000..49911c494 --- /dev/null +++ b/vendor/tabled/tests/settings/colorization.rs @@ -0,0 +1,64 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + object::{Cell, Object}, + themes::Colorization, + Color, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + chess_2x3, + Matrix::new(2, 3).with(Colorization::chess(color1(), color2())), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 1\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+" +); + +test_table!( + chess_3x3, + Matrix::new(3, 3).with(Colorization::chess(color1(), color2())), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 2\u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m2\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m2-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m2-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m2-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+" +); + +test_table!( + rows, + Matrix::new(2, 3).with(Colorization::rows([color1(), color2(), color3()])), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[1m \u{1b}[22m\u{1b}[1m1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-0\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-2\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\n+---+----------+----------+----------+" +); + +test_table!( + columns, + Matrix::new(2, 3).with(Colorization::columns([color1(), color2(), color3()])), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1mcolumn 1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m0-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+" +); + +test_table!( + by_row, + Matrix::new(2, 3).with(Colorization::by_row([color1(), color2(), color3()])), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1mcolumn 1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m0-0\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[1m \u{1b}[22m\u{1b}[1m1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-2\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\n+---+----------+----------+----------+" +); + +test_table!( + by_column, + Matrix::new(2, 3).with(Colorization::by_column([color1(), color2(), color3()])), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[1m \u{1b}[22m\u{1b}[1m1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-0\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-2\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\n+---+----------+----------+----------+" +); + +test_table!( + exact, + Matrix::new(2, 3).with(Colorization::exact([color1(), color2(), color3()], Cell::new(0, 0).and(Cell::new(1, 1)).and(Cell::new(2, 2)))), + "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m| column 0 | column 1 | column 2 |\n+---+----------+----------+----------+\n| 0 |\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m| 0-1 | 0-2 |\n+---+----------+----------+----------+\n| 1 | 1-0 |\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m| 1-2 |\n+---+----------+----------+----------+" +); + +fn color1() -> Color { + Color::BG_RED +} + +fn color2() -> Color { + Color::BG_BRIGHT_CYAN +} + +fn color3() -> Color { + Color::BOLD +} diff --git a/vendor/tabled/tests/settings/column_names_test.rs b/vendor/tabled/tests/settings/column_names_test.rs new file mode 100644 index 000000000..c2b23f1e3 --- /dev/null +++ b/vendor/tabled/tests/settings/column_names_test.rs @@ -0,0 +1,270 @@ +#![cfg(feature = "std")] + +use tabled::{ + grid::config::AlignmentHorizontal, + settings::{themes::ColumnNames, Color}, + Table, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + new, + Matrix::new(3, 3).with(ColumnNames::new(["1", "2", "3", "4"])), + "+1--+2---------+3---------+4---------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + new_more_names_then_columns, + Matrix::new(3, 3).with(ColumnNames::new(["1", "2", "3", "4", "5", "6", "7"])), + "+1--+2---------+3---------+4---------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + new_less_names_then_columns, + Matrix::new(3, 3).with(ColumnNames::new(["1", "2"])), + "+1--+2---------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + new_empty, + Matrix::new(3, 3).with(ColumnNames::new([""; 0])), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + default, + Matrix::new(3, 3).with(ColumnNames::default()), + "+N--+column 0+column 1+column 2+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+--------+--------+--------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+--------+--------+--------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+--------+--------+--------+" +); + +test_table!( + alignment_left, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Left)), + "+&str---+&str------+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + alignment_right, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Right)), + "+---&str+------&str+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + alignment_center, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Center)), + "+-&str--+---&str---+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + alignment_left_offset, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Left).set_offset(1)), + "+-&str--+-&str-----+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + alignment_left_offset_big, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Left).set_offset(30)), + "+------------------------------&str+------------------------------&str+" + "| Hello | World |" + "+----------------------------------+----------------------------------+" + "| and | looooong |" + "| | word |" + "+----------------------------------+----------------------------------+" +); + +test_table!( + alignment_left_offset_0, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Left).set_offset(0)), + "+&str---+&str------+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + alignment_right_offset, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Right).set_offset(1)), + "+--&str-+-----&str-+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + alignment_right_offset_big, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Right).set_offset(30)), + "+&str------------------------------+&str------------------------------+" + "| Hello | World |" + "+----------------------------------+----------------------------------+" + "| and | looooong |" + "| | word |" + "+----------------------------------+----------------------------------+" +); + +test_table!( + alignment_right_offset_0, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Right).set_offset(0)), + "+---&str+------&str+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + set_line, + Matrix::new(3, 3).with(ColumnNames::default().set_line(1)), + "+---+--------+--------+--------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+N--+column 0+column 1+column 2+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+--------+--------+--------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+--------+--------+--------+" +); + +test_table!( + set_line_max_out, + Matrix::new(3, 3).with(ColumnNames::default().set_line(100)), + "+---+--------+--------+--------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+--------+--------+--------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+--------+--------+--------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+--------+--------+--------+" +); + +test_table!( + set_line_0, + Matrix::new(3, 3).with(ColumnNames::default().set_line(0)), + "+N--+column 0+column 1+column 2+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+--------+--------+--------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+--------+--------+--------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+--------+--------+--------+" +); + +test_table!( + set_colors_some_some, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_colors([Some(Color::BG_BLACK), Some(Color::BG_BLUE)])), + "+\u{1b}[40m&\u{1b}[49m\u{1b}[40ms\u{1b}[49m\u{1b}[40mt\u{1b}[49m\u{1b}[40mr\u{1b}[49m---+\u{1b}[44m&\u{1b}[49m\u{1b}[44ms\u{1b}[49m\u{1b}[44mt\u{1b}[49m\u{1b}[44mr\u{1b}[49m------+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + set_colors_none_some, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_colors([None, Some(Color::BG_BLUE)])), + "+&str---+\u{1b}[44m&\u{1b}[49m\u{1b}[44ms\u{1b}[49m\u{1b}[44mt\u{1b}[49m\u{1b}[44mr\u{1b}[49m------+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + set_colors_none_none, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_colors([None, None])), + "+&str---+&str------+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); + +test_table!( + set_colors_empty, + Table::new([("Hello", "World"), ("and", "looooong\nword")]) + .with(ColumnNames::default().set_colors([None; 0])), + "+&str---+&str------+" + "| Hello | World |" + "+-------+----------+" + "| and | looooong |" + "| | word |" + "+-------+----------+" +); diff --git a/vendor/tabled/tests/settings/concat_test.rs b/vendor/tabled/tests/settings/concat_test.rs new file mode 100644 index 000000000..b988dcad9 --- /dev/null +++ b/vendor/tabled/tests/settings/concat_test.rs @@ -0,0 +1,140 @@ +#![cfg(feature = "std")] + +use tabled::settings::{Concat, Style}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + join_vertical_0, + Matrix::new(2, 3).insert((1, 0), "123").with(Style::psql()) + .with(Concat::vertical(Matrix::new(2, 3).to_table())) + .to_string(), + " N | column 0 | column 1 | column 2 " + "-----+----------+----------+----------" + " 123 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " +); + +test_table!( + join_vertical_1, + Matrix::new(2, 3) + .with(Concat::vertical(Matrix::new(2, 3).insert((1, 0), "123").with(Style::psql()))), + "+-----+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+-----+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+-----+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+-----+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+-----+----------+----------+----------+" + "| 123 | 0-0 | 0-1 | 0-2 |" + "+-----+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+-----+----------+----------+----------+" +); + +test_table!( + join_horizontal_0, + { + let mut table1 = Matrix::table(2, 3); + table1.with(Style::ascii()); + let mut table2 = Matrix::table(2, 3); + table2.with(Style::psql()); + table2.with(Concat::horizontal(table1)).to_string() + }, + " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 " + "---+----------+----------+----------+---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 " +); + +test_table!( + join_horizontal_1, + { + let mut table1 = Matrix::table(2, 3); + table1.with(Style::ascii()); + let mut table2 = Matrix::table(2, 3); + table2.with(Style::psql()); + table1.with(Concat::horizontal(table2)).to_string() + }, + "+---+----------+----------+----------+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+---+----------+----------+----------+" +); + +test_table!( + join_vertical_different_size, + { + let mut table1 = Matrix::table(2, 2); + table1.with(Style::psql()); + let mut table2 = Matrix::table(2, 3); + table2.with(Style::psql()); + table1.with(Concat::vertical(table2)).to_string() + }, + " N | column 0 | column 1 | " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | " + " 1 | 1-0 | 1-1 | " + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " +); + +test_table!( + join_horizontal_different_size, + { + let mut table1 = Matrix::table(2, 3); + table1.with(Style::psql()); + let mut table2 = Matrix::table(3, 3); + table2.with(Style::psql()); + table1.with(Concat::horizontal(table2)).to_string() + }, + " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 " + "---+----------+----------+----------+---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 " + " | | | | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + join_horizontal_with_not_default_empty_string, + { + let mut table1 = Matrix::table(2, 3); + table1.with(Style::psql()); + let mut table2 = Matrix::table(3, 3); + table2.with(Style::psql()); + table1.with(Concat::horizontal(table2).default_cell("NaN")).to_string() + }, + " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 " + "-----+----------+----------+----------+---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 " + " NaN | NaN | NaN | NaN | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + join_vertical_with_not_default_empty_string, + { + let mut table1 = Matrix::table(2, 2); + table1.with(Style::psql()); + let mut table2 = Matrix::table(2, 3); + table2.with(Style::psql()); + table1.with(Concat::vertical(table2).default_cell("NaN")).to_string() + }, + " N | column 0 | column 1 | NaN " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | NaN " + " 1 | 1-0 | 1-1 | NaN " + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " +); diff --git a/vendor/tabled/tests/settings/disable_test.rs b/vendor/tabled/tests/settings/disable_test.rs new file mode 100644 index 000000000..cf50acf84 --- /dev/null +++ b/vendor/tabled/tests/settings/disable_test.rs @@ -0,0 +1,83 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + locator::ByColumnName, + object::{Columns, Rows, Segment}, + style::{HorizontalLine, Style}, + Alignment, Disable, Modify, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + disable_rows, + Matrix::new(3, 3).with(Disable::row(Rows::new(1..=2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + disable_header, + Matrix::new(3, 3).with(Style::psql()).with(Disable::row(Rows::first())), + " 0 | 0-0 | 0-1 | 0-2 " + "---+-----+-----+-----" + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + disable_all_table_via_rows, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Disable::row(Columns::new(..))), + "" +); + +test_table!( + disable_header_with_new_styling, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Disable::row(Rows::new(..1))) + .with(Style::modern().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())])), + "┌───┬─────┬─────┬─────┐" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "├───┼─────┼─────┼─────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴─────┴─────┴─────┘" +); + +test_table!( + disable_columns, + Matrix::new(3, 3).with(Style::psql()).with(Disable::column(Columns::first())), + " column 0 | column 1 | column 2 " + "----------+----------+----------" + " 0-0 | 0-1 | 0-2 " + " 1-0 | 1-1 | 1-2 " + " 2-0 | 2-1 | 2-2 " +); + +test_table!( + disable_column_by_name, + Matrix::new(3, 3).with(Style::psql()) + .with(Disable::column(ByColumnName::new("column 1"))) + .with(Disable::column(ByColumnName::new("column 3"))), + " N | column 0 | column 2 " + "---+----------+----------" + " 0 | 0-0 | 0-2 " + " 1 | 1-0 | 1-2 " + " 2 | 2-0 | 2-2 " +); + +test_table!( + disable_all_table_via_columns, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Disable::column(Columns::new(..))), + "" +); diff --git a/vendor/tabled/tests/settings/duplicate_test.rs b/vendor/tabled/tests/settings/duplicate_test.rs new file mode 100644 index 000000000..6d15fbfac --- /dev/null +++ b/vendor/tabled/tests/settings/duplicate_test.rs @@ -0,0 +1,189 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + object::{Cell, Columns, Rows, Segment}, + Dup, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + dup_cell_to_cell, + Matrix::new(3, 3).with(Dup::new(Cell::new(0, 0), Cell::new(0, 1))), + "+----------+----------+----------+----------+" + "| column 0 | column 0 | column 1 | column 2 |" + "+----------+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+----------+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+----------+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+----------+----------+----------+----------+" +); + +test_table!( + dup_cell_to_column, + Matrix::new(3, 3).with(Dup::new(Columns::single(1), Cell::new(0, 0))), + "+---+---+----------+----------+" + "| N | N | column 1 | column 2 |" + "+---+---+----------+----------+" + "| 0 | N | 0-1 | 0-2 |" + "+---+---+----------+----------+" + "| 1 | N | 1-1 | 1-2 |" + "+---+---+----------+----------+" + "| 2 | N | 2-1 | 2-2 |" + "+---+---+----------+----------+" +); + +test_table!( + dup_row_to_row_single, + Matrix::new(3, 3).with(Dup::new(Rows::single(1), Rows::single(0))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_row_to_row_single_to_many, + Matrix::new(3, 3).with(Dup::new(Rows::new(1..3), Rows::single(0))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_row_to_row_single_to_all, + Matrix::new(3, 3).with(Dup::new(Rows::new(1..), Rows::single(0))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_row_to_column_single, + Matrix::new(3, 3).with(Dup::new(Columns::single(1), Rows::single(0))), + "+---+----------+----------+----------+" + "| N | N | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | column 0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | column 1 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | column 2 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_column_to_row_single, + Matrix::new(3, 3).with(Dup::new(Columns::single(1), Columns::single(0))), + "+---+---+----------+----------+" + "| N | N | column 1 | column 2 |" + "+---+---+----------+----------+" + "| 0 | 0 | 0-1 | 0-2 |" + "+---+---+----------+----------+" + "| 1 | 1 | 1-1 | 1-2 |" + "+---+---+----------+----------+" + "| 2 | 2 | 2-1 | 2-2 |" + "+---+---+----------+----------+" +); + +test_table!( + dup_row_to_column_single_repeat, + Matrix::new(4, 3).with(Dup::new(Columns::single(1), Rows::single(0))), + "+---+----------+----------+----------+" + "| N | N | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | column 0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | column 1 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | column 2 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" + "| 3 | N | 3-1 | 3-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_column_to_row_single_stop, + Matrix::new(4, 3).with(Dup::new(Rows::single(1), Columns::single(0))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | 0 | 1 | 2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" + "| 3 | 3-0 | 3-1 | 3-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_row_to_global, + Matrix::new(4, 3).with(Dup::new(Segment::all(), Rows::single(0))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" +); + +test_table!( + dup_column_to_global, + Matrix::new(4, 3).with(Dup::new(Segment::all(), Columns::single(0))), + "+---+---+---+---+" + "| N | 0 | 1 | 2 |" + "+---+---+---+---+" + "| 3 | N | 0 | 1 |" + "+---+---+---+---+" + "| 2 | 3 | N | 0 |" + "+---+---+---+---+" + "| 1 | 2 | 3 | N |" + "+---+---+---+---+" + "| 0 | 1 | 2 | 3 |" + "+---+---+---+---+" +); + +test_table!( + dup_empty_table, + Matrix::empty().with(Dup::new(Segment::all(), Columns::single(0))), + "" +); + +test_table!( + dup_invalid_target, + Matrix::new(4, 3).with(Dup::new(Segment::all(), Columns::single(99))), + Matrix::new(4, 3), +); + +test_table!( + dup_invalid_source, + Matrix::new(4, 3).with(Dup::new(Rows::single(99), Columns::first())), + Matrix::new(4, 3), +); diff --git a/vendor/tabled/tests/settings/extract_test.rs b/vendor/tabled/tests/settings/extract_test.rs new file mode 100644 index 000000000..ce4697d7a --- /dev/null +++ b/vendor/tabled/tests/settings/extract_test.rs @@ -0,0 +1,242 @@ +#![cfg(feature = "std")] + +use tabled::{ + builder::Builder, + settings::{ + object::{Rows, Segment}, + Alignment, Disable, Extract, Format, Modify, Padding, + }, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + extract_segment_full_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::segment(.., ..)), + "+-------+--------------+--------------+--------------+" + "| [N] | [column 0] | [column 1] | [column 2] |" + "+-------+--------------+--------------+--------------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-------+--------------+--------------+--------------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-------+--------------+--------------+--------------+" + "| [2] | [2-0] | [2-1] | [2-2] |" + "+-------+--------------+--------------+--------------+" +); + +test_table!( + extract_segment_skip_top_row_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::segment(1.., ..)), + "+-------+---------+---------+---------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-------+---------+---------+---------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-------+---------+---------+---------+" + "| [2] | [2-0] | [2-1] | [2-2] |" + "+-------+---------+---------+---------+" +); + +test_table!( + extract_segment_skip_column_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::segment(.., 1..)), + "+--------------+--------------+--------------+" + "| [column 0] | [column 1] | [column 2] |" + "+--------------+--------------+--------------+" + "| [0-0] | [0-1] | [0-2] |" + "+--------------+--------------+--------------+" + "| [1-0] | [1-1] | [1-2] |" + "+--------------+--------------+--------------+" + "| [2-0] | [2-1] | [2-2] |" + "+--------------+--------------+--------------+" +); + +test_table!( + extract_segment_bottom_right_square_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::segment(2.., 2..)), + "+---------+---------+" + "| [1-1] | [1-2] |" + "+---------+---------+" + "| [2-1] | [2-2] |" + "+---------+---------+" +); + +test_table!( + extract_segment_middle_section_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::segment(1..3, 1..)), + "+---------+---------+---------+" + "| [0-0] | [0-1] | [0-2] |" + "+---------+---------+---------+" + "| [1-0] | [1-1] | [1-2] |" + "+---------+---------+---------+" +); + +test_table!( + extract_segment_empty_test, + Matrix::new(3, 3).with(Extract::segment(1..1, 1..1)), + "" +); + +test_table!( + extract_rows_full_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::rows(..)), + "+-------+--------------+--------------+--------------+" + "| [N] | [column 0] | [column 1] | [column 2] |" + "+-------+--------------+--------------+--------------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-------+--------------+--------------+--------------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-------+--------------+--------------+--------------+" + "| [2] | [2-0] | [2-1] | [2-2] |" + "+-------+--------------+--------------+--------------+" +); + +test_table!( + extract_rows_empty_test, + Matrix::new(3, 3).with(Extract::rows(0..0)), + "" +); + +test_table!( + extract_rows_partial_view_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::rows(0..=2)), + "+-------+--------------+--------------+--------------+" + "| [N] | [column 0] | [column 1] | [column 2] |" + "+-------+--------------+--------------+--------------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-------+--------------+--------------+--------------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-------+--------------+--------------+--------------+" +); + +test_table!( + extract_columns_full_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::columns(..)), + "+-------+--------------+--------------+--------------+" + "| [N] | [column 0] | [column 1] | [column 2] |" + "+-------+--------------+--------------+--------------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-------+--------------+--------------+--------------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-------+--------------+--------------+--------------+" + "| [2] | [2-0] | [2-1] | [2-2] |" + "+-------+--------------+--------------+--------------+" +); + +test_table!( + extract_columns_empty_test, + Matrix::new(3, 3).with(Extract::columns(0..0)), + "" +); + +test_table!( + extract_columns_partial_view_test, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) + .with(Extract::columns(0..2)), + "+-------+--------------+" + "| [N] | [column 0] |" + "+-------+--------------+" + "| [0] | [0-0] |" + "+-------+--------------+" + "| [1] | [1-0] |" + "+-------+--------------+" + "| [2] | [2-0] |" + "+-------+--------------+" +); + +test_table!( + extract_inside_test, + Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(1..2, 1..2)), + "+-----+" + "| 1-0 |" + "+-----+" +); + +test_table!( + extract_left_test, + Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(.., ..1)), + "+---+" + "| 0 |" + "+---+" + "| 1 |" + "+---+" + "| 2 |" + "+---+" +); + +test_table!( + extract_right_test, + Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(.., 2..)), + "+-----+-----+" + "| 0-1 | 0-2 |" + "+-----+-----+" + "| 1-1 | 1-2 |" + "+-----+-----+" + "| 2-1 | 2-2 |" + "+-----+-----+" +); + +test_table!( + extract_top_test, + Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(..1, ..)), + "+---+-----+-----+-----+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+-----+-----+-----+" +); + +test_table!( + extract_bottom_test, + Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(2.., ..)), + "+---+-----+-----+-----+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+-----+-----+-----+" +); + +test_table!( + extract_all_test, + Matrix::new(3, 3) + .with(Disable::row(Rows::first())) + .with(Extract::segment(3.., 3..)), + "" +); + +test_table!( + extract_empty_test, + Builder::default().build().with(Extract::segment(.., ..)), + "" +); diff --git a/vendor/tabled/tests/settings/format_test.rs b/vendor/tabled/tests/settings/format_test.rs new file mode 100644 index 000000000..82585ca62 --- /dev/null +++ b/vendor/tabled/tests/settings/format_test.rs @@ -0,0 +1,269 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + object::{Cell, Columns, Object, Rows, Segment}, + Alignment, Format, Modify, Padding, Style, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +#[cfg(feature = "color")] +use owo_colors::OwoColorize; + +test_table!( + formatting_full_test, + Matrix::new(3, 3).with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + "+-----+------------+------------+------------+" + "| [N] | [column 0] | [column 1] | [column 2] |" + "+-----+------------+------------+------------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-----+------------+------------+------------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-----+------------+------------+------------+" + "| [2] | [2-0] | [2-1] | [2-2] |" + "+-----+------------+------------+------------+" +); + +test_table!( + formatting_head_test, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::first()).with(Format::content(|s| format!(":{s}")))), + "| :N | :column 0 | :column 1 | :column 2 |" + "|----|-----------|-----------|-----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + formatting_row_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!("<{s}>")))), + " N | column 0 | column 1 | column 2 " + "-----+----------+----------+----------" + " <0> | <0-0> | <0-1> | <0-2> " + " <1> | <1-0> | <1-1> | <1-2> " + " <2> | <2-0> | <2-1> | <2-2> " +); + +test_table!( + formatting_column_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Columns::single(0)).with(Format::content(|s| format!("(x) {s}")))), + " (x) N | column 0 | column 1 | column 2 " + "-------+----------+----------+----------" + " (x) 0 | 0-0 | 0-1 | 0-2 " + " (x) 1 | 1-0 | 1-1 | 1-2 " + " (x) 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + formatting_multiline_test, + Matrix::new(3, 3) + .insert((2, 2), "E\nnde\navou\nros") + .insert((3, 2), "Red\nHat") + .insert((3, 3), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("(x) {s}")).multiline())), + " (x) N | (x) column 0 | (x) column 1 | (x) column 2 " + "-------+--------------+--------------+--------------" + " (x) 0 | (x) 0-0 | (x) 0-1 | (x) 0-2 " + " (x) 1 | (x) 1-0 | (x) E | (x) 1-2 " + " | | (x) nde | " + " | | (x) avou | " + " | | (x) ros | " + " (x) 2 | (x) 2-0 | (x) Red | (x) https:// " + " | | (x) Hat | (x) www " + " | | | (x) . " + " | | | (x) redhat " + " | | | (x) .com " + " | | | (x) /en " +); + +test_table!( + formatting_cell_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Cell::new(0, 0)).with(Format::content(|s| format!("(x) {s}")))) + .with(Modify::new(Cell::new(0, 1)).with(Format::content(|s| format!("(x) {s}")))) + .with(Modify::new(Cell::new(0, 2)).with(Format::content(|s| format!("(x) {s}")))), + " (x) N | (x) column 0 | (x) column 1 | column 2 " + "-------+--------------+--------------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + formatting_combination_and_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with( + Modify::new(Columns::single(0).and(Rows::single(0))) + .with(Format::content(|s| format!("(x) {s}"))), + ), + " (x) N | (x) column 0 | (x) column 1 | (x) column 2 " + "-------+--------------+--------------+--------------" + " (x) 0 | 0-0 | 0-1 | 0-2 " + " (x) 1 | 1-0 | 1-1 | 1-2 " + " (x) 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + formatting_combination_not_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with( + Modify::new(Columns::single(0).and(Rows::single(0)).not(Cell::new(0, 0))) + .with(Format::content(|s| format!("(x) {s}"))), + ), + " N | (x) column 0 | (x) column 1 | (x) column 2 " + "-------+--------------+--------------+--------------" + " (x) 0 | 0-0 | 0-1 | 0-2 " + " (x) 1 | 1-0 | 1-1 | 1-2 " + " (x) 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + formatting_combination_inverse_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Columns::single(0).inverse()).with(Format::content(|s| format!("(x) {s}")))), + " N | (x) column 0 | (x) column 1 | (x) column 2 " + "---+--------------+--------------+--------------" + " 0 | (x) 0-0 | (x) 0-1 | (x) 0-2 " + " 1 | (x) 1-0 | (x) 1-1 | (x) 1-2 " + " 2 | (x) 2-0 | (x) 2-1 | (x) 2-2 " +); + +test_table!( + formatting_combination_intersect_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with( + Modify::new(Columns::new(1..3).intersect(Rows::new(1..3))) + .with(Format::content(|s| format!("(x) {s}"))), + ), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | (x) 0-0 | (x) 0-1 | 0-2 " + " 1 | (x) 1-0 | (x) 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + formatting_using_lambda_test, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::first()).with(Format::content(|s| format!(":{s}")))), + "| :N | :column 0 | :column 1 | :column 2 |" + "|----|-----------|-----------|-----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + formatting_using_function_test, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::first()).with(Format::content(str::to_uppercase))), + "| N | COLUMN 0 | COLUMN 1 | COLUMN 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + format_with_index, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::first()).with(Format::positioned(|a, (b, c)| match (b, c) { + (0, 0) => "(0, 0)".to_string(), + (0, 1) => "(0, 1)".to_string(), + (0, 2) => "(0, 2)".to_string(), + _ => a.to_string(), + }))), + "| (0, 0) | (0, 1) | (0, 2) | column 2 |" + "|--------|--------|--------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + format_doesnt_change_padding, + Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) + .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), + "+-------+--------------+--------------+--------------+" + "| [N] | [column 0] | [column 1] | [column 2] |" + "+-------+--------------+--------------+--------------+" + "| [0] | [0-0] | [0-1] | [0-2] |" + "+-------+--------------+--------------+--------------+" + "| [1] | [1-0] | [1-1] | [1-2] |" + "+-------+--------------+--------------+--------------+" + "| [2] | [2-0] | [2-1] | [2-2] |" + "+-------+--------------+--------------+--------------+" +); + +test_table!( + formatting_content_str_test, + Matrix::new(3, 3).with(Modify::new(Segment::all()).with(Format::content(|_| String::from("Hello World")))), + "+-------------+-------------+-------------+-------------+" + "| Hello World | Hello World | Hello World | Hello World |" + "+-------------+-------------+-------------+-------------+" + "| Hello World | Hello World | Hello World | Hello World |" + "+-------------+-------------+-------------+-------------+" + "| Hello World | Hello World | Hello World | Hello World |" + "+-------------+-------------+-------------+-------------+" + "| Hello World | Hello World | Hello World | Hello World |" + "+-------------+-------------+-------------+-------------+" +); + +#[cfg(feature = "color")] +test_table!( + color_test, + Matrix::new(3, 3) + .with(Style::psql()) + .with( + Modify::new(Columns::new(..1).and(Columns::new(2..))) + .with(Format::content(|s| s.red().to_string())), + ) + .with(Modify::new(Columns::new(1..2)).with(Format::content(|s| s.blue().to_string()))), + " \u{1b}[31mN\u{1b}[39m | \u{1b}[34mcolumn 0\u{1b}[39m | \u{1b}[31mcolumn 1\u{1b}[39m | \u{1b}[31mcolumn 2\u{1b}[39m " + "---+----------+----------+----------" + " \u{1b}[31m0\u{1b}[39m | \u{1b}[34m0-0\u{1b}[39m | \u{1b}[31m0-1\u{1b}[39m | \u{1b}[31m0-2\u{1b}[39m " + " \u{1b}[31m1\u{1b}[39m | \u{1b}[34m1-0\u{1b}[39m | \u{1b}[31m1-1\u{1b}[39m | \u{1b}[31m1-2\u{1b}[39m " + " \u{1b}[31m2\u{1b}[39m | \u{1b}[34m2-0\u{1b}[39m | \u{1b}[31m2-1\u{1b}[39m | \u{1b}[31m2-2\u{1b}[39m " +); + +#[cfg(feature = "color")] +test_table!( + color_multiline_test, + Matrix::new(3, 3) + .insert((2, 2), "E\nnde\navou\nros") + .insert((3, 2), "Red\nHat") + .insert((3, 3), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new(Columns::new(..1)).with(Format::content(|s| s.red().to_string()).multiline())) + .with(Modify::new(Columns::new(1..2)).with(Format::content(|s| s.blue().to_string()).multiline())) + .with(Modify::new(Columns::new(2..)).with(Format::content(|s| s.green().to_string()).multiline())), + " \u{1b}[31mN\u{1b}[39m | \u{1b}[34mcolumn 0\u{1b}[39m | \u{1b}[32mcolumn 1\u{1b}[39m | \u{1b}[32mcolumn 2\u{1b}[39m " + "---+----------+----------+----------\n \u{1b}[31m0\u{1b}[39m | \u{1b}[34m0-0\u{1b}[39m | \u{1b}[32m0-1\u{1b}[39m | \u{1b}[32m0-2\u{1b}[39m " + " \u{1b}[31m1\u{1b}[39m | \u{1b}[34m1-0\u{1b}[39m | \u{1b}[32mE\u{1b}[39m | \u{1b}[32m1-2\u{1b}[39m " + " | | \u{1b}[32mnde\u{1b}[39m | " + " | | \u{1b}[32mavou\u{1b}[39m | " + " | | \u{1b}[32mros\u{1b}[39m | " + " \u{1b}[31m2\u{1b}[39m | \u{1b}[34m2-0\u{1b}[39m | \u{1b}[32mRed\u{1b}[39m | \u{1b}[32mhttps://\u{1b}[39m " + " | | \u{1b}[32mHat\u{1b}[39m | \u{1b}[32mwww\u{1b}[39m \n | | | \u{1b}[32m.\u{1b}[39m " + " | | | \u{1b}[32mredhat\u{1b}[39m " + " | | | \u{1b}[32m.com\u{1b}[39m " + " | | | \u{1b}[32m/en\u{1b}[39m " +); diff --git a/vendor/tabled/tests/settings/formatting_test.rs b/vendor/tabled/tests/settings/formatting_test.rs new file mode 100644 index 000000000..70f925a8b --- /dev/null +++ b/vendor/tabled/tests/settings/formatting_test.rs @@ -0,0 +1,68 @@ +#![cfg(feature = "std")] + +use tabled::settings::{formatting::Justification, object::Columns, Color, Modify}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + justification, + Matrix::new(3, 3).with(Justification::new('#')), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | ##0-0### | ##0-1### | ##0-2### |" + "+---+----------+----------+----------+" + "| 1 | ##1-0### | ##1-1### | ##1-2### |" + "+---+----------+----------+----------+" + "| 2 | ##2-0### | ##2-1### | ##2-2### |" + "+---+----------+----------+----------+" +); + +test_table!( + justification_color, + Matrix::new(3, 3).with(Justification::new('#').color(Color::BG_RED)), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | \u{1b}[41m##\u{1b}[49m0-0\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m0-1\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m0-2\u{1b}[41m###\u{1b}[49m |" + "+---+----------+----------+----------+" + "| 1 | \u{1b}[41m##\u{1b}[49m1-0\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m1-1\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m1-2\u{1b}[41m###\u{1b}[49m |" + "+---+----------+----------+----------+" + "| 2 | \u{1b}[41m##\u{1b}[49m2-0\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m2-1\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m2-2\u{1b}[41m###\u{1b}[49m |" + "+---+----------+----------+----------+" +); + +test_table!( + justification_columns, + Matrix::new(3, 3) + .with(Modify::new(Columns::single(1)).with(Justification::new('#'))) + .with(Modify::new(Columns::single(2)).with(Justification::new('@'))) + .with(Modify::new(Columns::single(3)).with(Justification::new('$'))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | ##0-0### | @@0-1@@@ | $$0-2$$$ |" + "+---+----------+----------+----------+" + "| 1 | ##1-0### | @@1-1@@@ | $$1-2$$$ |" + "+---+----------+----------+----------+" + "| 2 | ##2-0### | @@2-1@@@ | $$2-2$$$ |" + "+---+----------+----------+----------+" +); + +test_table!( + justification_color_columns, + Matrix::new(3, 3) + .with(Modify::new(Columns::single(1)).with(Justification::new('#').color(Color::BG_BLUE))) + .with(Modify::new(Columns::single(2)).with(Justification::new('@').color(Color::BG_RED))) + .with(Modify::new(Columns::single(3)).with(Justification::new('$').color(Color::BG_WHITE))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | \u{1b}[44m##\u{1b}[49m0-0\u{1b}[44m###\u{1b}[49m | \u{1b}[41m@@\u{1b}[49m0-1\u{1b}[41m@@@\u{1b}[49m | \u{1b}[47m$$\u{1b}[49m0-2\u{1b}[47m$$$\u{1b}[49m |" + "+---+----------+----------+----------+" + "| 1 | \u{1b}[44m##\u{1b}[49m1-0\u{1b}[44m###\u{1b}[49m | \u{1b}[41m@@\u{1b}[49m1-1\u{1b}[41m@@@\u{1b}[49m | \u{1b}[47m$$\u{1b}[49m1-2\u{1b}[47m$$$\u{1b}[49m |" + "+---+----------+----------+----------+" + "| 2 | \u{1b}[44m##\u{1b}[49m2-0\u{1b}[44m###\u{1b}[49m | \u{1b}[41m@@\u{1b}[49m2-1\u{1b}[41m@@@\u{1b}[49m | \u{1b}[47m$$\u{1b}[49m2-2\u{1b}[47m$$$\u{1b}[49m |" + "+---+----------+----------+----------+" +); diff --git a/vendor/tabled/tests/settings/height_test.rs b/vendor/tabled/tests/settings/height_test.rs new file mode 100644 index 000000000..48d6a6364 --- /dev/null +++ b/vendor/tabled/tests/settings/height_test.rs @@ -0,0 +1,263 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + object::{Columns, Segment}, + Alignment, Format, Height, Modify, Style, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +#[cfg(feature = "color")] +use owo_colors::OwoColorize; + +test_table!( + cell_height_increase, + Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Columns::first()) + .with(Height::increase(3)) + ) + .with(Modify::new(Segment::all()).with( + Alignment::center_vertical() + )), + "| N | | | |" + "| | column 0 | column 1 | column 2 |" + "| | | | |" + "|---|----------|----------|----------|" + "| 0 | | | |" + "| | 0-0 | 0-1 | 0-2 |" + "| | | | |" + "| 1 | | | |" + "| | 1-0 | 1-1 | 1-2 |" + "| | | | |" + "| 2 | | | |" + "| | 2-0 | 2-1 | 2-2 |" + "| | | | |" +); + +test_table!( + table_height_increase, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) + .with(Height::increase(10)), + "| | column 0 | column 1 | column 2 |" + "| N | | | |" + "| | | | |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| | | | |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| | | | |" + "| 2 | 2-0 | 2-1 | 2-2 |" + "| | | | |" +); + +test_table!( + cell_height_increase_zero, + Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Columns::first()) + .with(Height::increase(0)) + ) + .with(Modify::new(Segment::all()).with( + Alignment::center_vertical() + )), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + table_height_increase_zero, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) + .with(Height::increase(0)), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + cell_height_limit, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) + .with( + Modify::new(Columns::first()) + .with(Height::limit(1)) + ) + .with(Modify::new(Segment::all()).with( + Alignment::center_vertical() + )), + "| xxxx | column 0 | column 1 | column 2 |" + "|------|----------|----------|----------|" + "| xxxx | 0-0 | 0-1 | 0-2 |" + "| xxxx | 1-0 | 1-1 | 1-2 |" + "| xxxx | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + table_height_limit, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) + .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) + .with(Height::limit(10)), + "| xxxx | column 0 | column 1 | column 2 |" + "| Nxxxx | | | |" + "|-------|----------|----------|----------|" + "| xxxx | 0-0 | 0-1 | 0-2 |" + "| 0xxxx | | | |" + "| xxxx | 1-0 | 1-1 | 1-2 |" + "| 1xxxx | | | |" + "| xxxx | 2-0 | 2-1 | 2-2 |" + "| 2xxxx | | | |" + "| xxxx | | | |" +); + +test_table!( + table_height_limit_style_change_after, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) + .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) + .with(Height::limit(7)), + "| xxxx | column 0 | column 1 | column 2 |" + "|-------|----------|----------|----------|" + "| xxxx | 0-0 | 0-1 | 0-2 |" + "| xxxx | 1-0 | 1-1 | 1-2 |" + "| 1xxxx | | | |" + "| xxxx | 2-0 | 2-1 | 2-2 |" + "| 2xxxx | | | |" +); + +test_table!( + cell_height_limit_zero, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) + .with( + Modify::new(Columns::first()) + .with(Height::limit(0)) + ) + .with(Modify::new(Segment::all()).with( + Alignment::center_vertical() + )), + "| | column 0 | column 1 | column 2 |" + "|--|----------|----------|----------|" + "| | 0-0 | 0-1 | 0-2 |" + "| | 1-0 | 1-1 | 1-2 |" + "| | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + table_height_limit_zero, + Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Columns::new(..)) + .with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n"))) + ) + .with(Height::limit(0)), + "|--|--|--|--|" +); + +test_table!( + table_height_limit_zero_1, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Height::limit(0)) + .with( + Modify::new(Columns::new(..)).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n"))) + ), + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| | | | |" + "|------|------|------|------|" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| | | | |" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| | | | |" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| xxxx | xxxx | xxxx | xxxx |" + "| | | | |" +); + +#[cfg(feature = "color")] +test_table!( + cell_height_limit_colored, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n").red().to_string()))) + .with( + Modify::new(Columns::first()) + .with(Height::limit(1)) + ) + .with(Modify::new(Segment::all()).with( + Alignment::center_vertical() + )), + "| \u{1b}[31mxxxx\u{1b}[39m | column 0 | column 1 | column 2 |" + "|------|----------|----------|----------|" + "| \u{1b}[31mxxxx\u{1b}[39m | 0-0 | 0-1 | 0-2 |" + "| \u{1b}[31mxxxx\u{1b}[39m | 1-0 | 1-1 | 1-2 |" + "| \u{1b}[31mxxxx\u{1b}[39m | 2-0 | 2-1 | 2-2 |" +); + +#[cfg(feature = "color")] +test_table!( + table_height_limit_colored, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n").blue().on_green().to_string()))) + .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) + .with(Height::limit(10)), + "| \u{1b}[34;42mxxxx\u{1b}[39m\u{1b}[49m | column 0 | column 1 | column 2 |" + "| \u{1b}[34m\u{1b}[42mNxxxx\u{1b}[39m\u{1b}[49m | | | |" + "|-------|----------|----------|----------|" + "| \u{1b}[34;42mxxxx\u{1b}[39m\u{1b}[49m | 0-0 | 0-1 | 0-2 |" + "| \u{1b}[34m\u{1b}[42m0xxxx\u{1b}[39m\u{1b}[49m | | | |" + "| \u{1b}[34;42mxxxx\u{1b}[39m\u{1b}[49m | 1-0 | 1-1 | 1-2 |" + "| \u{1b}[34m\u{1b}[42m1xxxx\u{1b}[39m\u{1b}[49m | | | |" + "| \u{1b}[34;42mxxxx\u{1b}[39m\u{1b}[49m | 2-0 | 2-1 | 2-2 |" + "| \u{1b}[34m\u{1b}[42m2xxxx\u{1b}[39m\u{1b}[49m | | | |" + "| \u{1b}[34m\u{1b}[42mxxxx\u{1b}[39m\u{1b}[49m | | | |" +); + +#[cfg(feature = "macros")] +test_table!( + cell_height_1x1, + tabled::row![tabled::col!["SGML"].with(Height::increase(4))], + "+----------+" + "| +------+ |" + "| | SGML | |" + "| | | |" + "| +------+ |" + "+----------+" +); + +#[cfg(feature = "macros")] +test_table!( + cell_height_1x1_no_top_border, + tabled::row![tabled::col!["SGML"].with(Style::ascii().remove_top()).with(Height::increase(4))], + "+----------+" + "| | SGML | |" + "| | | |" + "| | | |" + "| +------+ |" + "+----------+" +); diff --git a/vendor/tabled/tests/settings/highlingt_test.rs b/vendor/tabled/tests/settings/highlingt_test.rs new file mode 100644 index 000000000..cb5ce75f3 --- /dev/null +++ b/vendor/tabled/tests/settings/highlingt_test.rs @@ -0,0 +1,419 @@ +#![cfg(feature = "std")] + +use tabled::{ + builder::Builder, + settings::{ + highlight::Highlight, + object::{Cell, Columns, Frame, Object, Rows, Segment}, + style::{Border, Style}, + }, +}; + +use crate::matrix::Matrix; +use testing_table::{static_table, test_table}; + +test_table!( + highlingt_object_exceeds_boundaries, + Matrix::new(3, 3).with(Style::modern()).with(Highlight::new(Cell::new(1000, 0), Border::filled('+'))), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + highlingt_empty_table, + Builder::default() + .build() + .with(Highlight::new(Segment::all(), Border::filled('+'))), + "" +); + +test_table!( + highlingt_cell, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new(Cell::new(0, 0), Border::filled('+'))) + .with(Highlight::new(Cell::new(1, 1), Border::filled('*'))), + "+++++──────────┬──────────┬──────────┐" + "+ N + column 0 │ column 1 │ column 2 │" + "++++************──────────┼──────────┤" + "│ 0 * 0-0 * 0-1 │ 0-2 │" + "├───************──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + highlingt_row, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new(Rows::single(0), Border::filled('+'))) + .with(Highlight::new(Rows::single(3), Border::filled('*'))), + "++++++++++++++++++++++++++++++++++++++" + "+ N │ column 0 │ column 1 │ column 2 +" + "++++++++++++++++++++++++++++++++++++++" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "**************************************" + "* 2 │ 2-0 │ 2-1 │ 2-2 *" + "**************************************" +); + +test_table!( + highlingt_column, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new(Columns::single(0), Border::filled('+'))) + .with(Highlight::new(Columns::single(2), Border::filled('*'))), + "+++++──────────************──────────┐" + "+ N + column 0 * column 1 * column 2 │" + "+───+──────────*──────────*──────────┤" + "+ 0 + 0-0 * 0-1 * 0-2 │" + "+───+──────────*──────────*──────────┤" + "+ 1 + 1-0 * 1-1 * 1-2 │" + "+───+──────────*──────────*──────────┤" + "+ 2 + 2-0 * 2-1 * 2-2 │" + "+++++──────────************──────────┘" +); + +test_table!( + highlingt_row_range, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new(Rows::new(1..3), Border::filled('+'))), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "++++++++++++++++++++++++++++++++++++++" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "++++++++++++++++++++++++++++++++++++++" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + highlingt_column_range, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new(Columns::new(..2), Border::filled('+'))), + "++++++++++++++++──────────┬──────────┐" + "+ N │ column 0 + column 1 │ column 2 │" + "+───┼──────────+──────────┼──────────┤" + "+ 0 │ 0-0 + 0-1 │ 0-2 │" + "+───┼──────────+──────────┼──────────┤" + "+ 1 │ 1-0 + 1-1 │ 1-2 │" + "+───┼──────────+──────────┼──────────┤" + "+ 2 │ 2-0 + 2-1 │ 2-2 │" + "++++++++++++++++──────────┴──────────┘" +); + +test_table!( + highlingt_frame, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new( + Frame, + Border::filled('+') + .corner_top_left('*') + .corner_top_right('#') + .corner_bottom_left('@') + .corner_bottom_right('.'), + )), + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "@++++++++++++++++++++++++++++++++++++." +); + +test_table!( + highlingt_full, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new( + Segment::all(), + Border::filled('+') + .corner_top_left('*') + .corner_top_right('#') + .corner_bottom_left('@') + .corner_bottom_right('.'), + )), + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "@++++++++++++++++++++++++++++++++++++." +); + +test_table!( + highlingt_single_column, + Matrix::table(3, 0) + .with(Style::modern()) + .with(Highlight::new(Cell::new(0, 0), Border::default().left('*').top('x'))) + .with(Highlight::new(Rows::new(1..3), Border::default().left('n'))), + "┌xxx┐" + "* N │" + "├───┤" + "n 0 │" + "n───┤" + "n 1 │" + "├───┤" + "│ 2 │" + "└───┘" +); + +test_table!( + highlingt_several_times, + Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new(Frame, Border::filled('*'))) + .with(Highlight::new(Cell::new(1, 1), Border::filled('#'))) + .with(Highlight::new(Columns::single(3), Border::filled('x'))), + "**************************xxxxxxxxxxxx" + "* N │ column 0 │ column 1 x column 2 x" + "*───############──────────x──────────x" + "* 0 # 0-0 # 0-1 x 0-2 x" + "*───############──────────x──────────x" + "* 1 │ 1-0 │ 1-1 x 1-2 x" + "*───┼──────────┼──────────x──────────x" + "* 2 │ 2-0 │ 2-1 x 2-2 x" + "**************************xxxxxxxxxxxx" +); + +// @todo +// +// #[test] +// fn highlingt_empty_border() { +// let data = create_vector::<3, 3>(); +// let table = Table::new(&data) +// .with(Style::modern()) +// .with(Highlight::new(Frame, Border::empty())) +// .to_string(); + +// let expected = static_table!( +// " N │ column 0 │ column 1 │ column 2 " +// "─── ──────────" +// " 0 0-0 │ 0-1 0-2 " +// "─── ──────────┼────────── ──────────" +// " 1 1-0 │ 1-1 1-2 " +// "─── ──────────" +// " 2 │ 2-0 │ 2-1 │ 2-2 " +// ); + +// assert_eq!(table, expected); +// } + +#[test] +fn highlingt_complex_figures() { + macro_rules! test_highlight { + ($object:expr, $expected:expr,) => { + let border = Border::filled('+') + .corner_top_left('*') + .corner_top_right('#') + .corner_bottom_left('@') + .corner_bottom_right('.'); + + let table = Matrix::new(3, 3) + .with(Style::modern()) + .with(Highlight::new($object, border)) + .to_string(); + + assert_eq!(table, $expected); + }; + } + + test_highlight!( + Segment::all().not(Segment::new(2.., 1..3)), + static_table!( + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+───*+++++++++++++++++++++#──────────+" + "+ 1 + 1-0 │ 1-1 + 1-2 +" + "+───+──────────┼──────────+──────────+" + "+ 2 + 2-0 │ 2-1 + 2-2 +" + "@+++.──────────┴──────────@++++++++++." + ), + ); + + test_highlight!( + Segment::all() + .not(Segment::new(0..1, 1..3)) + .not(Columns::single(0)), + static_table!( + "┌───┬──────────┬──────────*++++++++++#" + "│ N │ column 0 │ column 1 + column 2 +" + "├───*+++++++++++++++++++++.──────────+" + "│ 0 + 0-0 │ 0-1 │ 0-2 +" + "├───+──────────┼──────────┼──────────+" + "│ 1 + 1-0 │ 1-1 │ 1-2 +" + "├───+──────────┼──────────┼──────────+" + "│ 2 + 2-0 │ 2-1 │ 2-2 +" + "└───@++++++++++++++++++++++++++++++++." + ), + ); + + test_highlight!( + Segment::all().not(Segment::new(0..1, 1..3)), + static_table!( + "*+++#──────────┬──────────*++++++++++#" + "+ N + column 0 │ column 1 + column 2 +" + "+───@+++++++++++++++++++++.──────────+" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "@++++++++++++++++++++++++++++++++++++." + ), + ); + + test_highlight!( + Segment::all().not(Segment::new(1..2, 1..3)), + static_table!( + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "+───*+++++++++++++++++++++#──────────+" + "+ 0 + 0-0 │ 0-1 + 0-2 +" + "+───@+++++++++++++++++++++.──────────+" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "@++++++++++++++++++++++++++++++++++++." + ), + ); + + test_highlight!( + Cell::new(0, 0) + .and(Cell::new(3, 3)) + .and(Cell::new(0, 3)) + .and(Cell::new(3, 0)), + static_table!( + "*+++#──────────┬──────────*++++++++++#" + "+ N + column 0 │ column 1 + column 2 +" + "@+++.──────────┼──────────@++++++++++." + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "*+++#──────────┼──────────*++++++++++#" + "+ 2 + 2-0 │ 2-1 + 2-2 +" + "@+++.──────────┴──────────@++++++++++." + ), + ); + + test_highlight!( + Rows::single(0).and(Rows::single(3)), + static_table!( + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "@++++++++++++++++++++++++++++++++++++." + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "*++++++++++++++++++++++++++++++++++++#" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "@++++++++++++++++++++++++++++++++++++." + ), + ); + + test_highlight!( + Columns::single(0).and(Columns::single(3)), + static_table!( + "*+++#──────────┬──────────*++++++++++#" + "+ N + column 0 │ column 1 + column 2 +" + "+───+──────────┼──────────+──────────+" + "+ 0 + 0-0 │ 0-1 + 0-2 +" + "+───+──────────┼──────────+──────────+" + "+ 1 + 1-0 │ 1-1 + 1-2 +" + "+───+──────────┼──────────+──────────+" + "+ 2 + 2-0 │ 2-1 + 2-2 +" + "@+++.──────────┴──────────@++++++++++." + ), + ); + + test_highlight!( + Segment::all().not(Cell::new(3, 1).and(Cell::new(3, 2))), + static_table!( + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+───┼──────────┼──────────┼──────────+" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "+───*+++++++++++++++++++++#──────────+" + "+ 2 + 2-0 │ 2-1 + 2-2 +" + "@+++.──────────┴──────────@++++++++++." + ), + ); + + test_highlight!( + Rows::single(0) + .and(Cell::new(1, 1).and(Cell::new(1, 2))) + .and(Cell::new(2, 3)), + static_table!( + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "@+++#──────────┼──────────*++++++++++." + "│ 0 + 0-0 │ 0-1 + 0-2 │" + "├───@+++++++++++++++++++++*++++++++++#" + "│ 1 │ 1-0 │ 1-1 + 1-2 +" + "├───┼──────────┼──────────@++++++++++." + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" + ), + ); + + test_highlight!( + Segment::all() + .not(Segment::new(2.., 0..3)) + .not(Cell::new(1, 0)), + static_table!( + "*++++++++++++++++++++++++++++++++++++#" + "+ N │ column 0 │ column 1 │ column 2 +" + "@+++#──────────┼──────────┼──────────+" + "│ 0 + 0-0 │ 0-1 │ 0-2 +" + "├───@+++++++++++++++++++++#──────────+" + "│ 1 │ 1-0 │ 1-1 + 1-2 +" + "├───┼──────────┼──────────+──────────+" + "│ 2 │ 2-0 │ 2-1 + 2-2 +" + "└───┴──────────┴──────────@++++++++++." + ), + ); + + test_highlight!( + Segment::all() + .not(Segment::new(..1, 1..)) + .not(Segment::new(1..2, 2..)) + .not(Cell::new(2, 3)), + static_table!( + "*+++#──────────┬──────────┬──────────┐" + "+ N + column 0 │ column 1 │ column 2 │" + "+───@++++++++++#──────────┼──────────┤" + "+ 0 │ 0-0 + 0-1 │ 0-2 │" + "+───┼──────────@++++++++++#──────────┤" + "+ 1 │ 1-0 │ 1-1 + 1-2 │" + "+───┼──────────┼──────────@++++++++++#" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "@++++++++++++++++++++++++++++++++++++." + ), + ); +} diff --git a/vendor/tabled/tests/settings/margin_test.rs b/vendor/tabled/tests/settings/margin_test.rs new file mode 100644 index 000000000..513287350 --- /dev/null +++ b/vendor/tabled/tests/settings/margin_test.rs @@ -0,0 +1,195 @@ +#![cfg(feature = "std")] + +use tabled::settings::{object::Cell, Border, Highlight, Margin, Modify, Span, Style, Width}; + +use crate::matrix::Matrix; +use testing_table::{is_lines_equal, static_table, test_table}; + +#[cfg(feature = "color")] +use ::{owo_colors::OwoColorize, std::convert::TryFrom, tabled::settings::Color}; + +test_table!( + margin_with_table_based_on_grid_borders, + Matrix::new(3, 3) + .with(Style::extended()) + .with(Highlight::new(Cell::new(0, 0), Border::filled('+'))) + .with(Highlight::new(Cell::new(1, 1), Border::filled('*'))) + .with(Margin::new(1, 2, 1, 2).fill('>', '<', 'V', '^')), + "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" + ">+++++══════════╦══════════╦══════════╗<<" + ">+ N + column 0 ║ column 1 ║ column 2 ║<<" + ">++++************══════════╬══════════╣<<" + ">║ 0 * 0-0 * 0-1 ║ 0-2 ║<<" + ">╠═══************══════════╬══════════╣<<" + ">║ 1 ║ 1-0 ║ 1-1 ║ 1-2 ║<<" + ">╠═══╬══════════╬══════════╬══════════╣<<" + ">║ 2 ║ 2-0 ║ 2-1 ║ 2-2 ║<<" + ">╚═══╩══════════╩══════════╩══════════╝<<" + "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" +); + +test_table!( + margin_without_table_based_on_grid_borders, + Matrix::new(3, 3) + .insert((3, 2), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new(Cell::new(3, 2)).with(Span::column(2))) + .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')), + "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" + "> N | column 0 | column 1 | column 2 <" + ">---+----------+----------+----------<" + "> 0 | 0-0 | 0-1 | 0-2 <" + "> 1 | 1-0 | 1-1 | 1-2 <" + "> 2 | 2-0 | https:// <" + "> | | www <" + "> | | . <" + "> | | redhat <" + "> | | .com <" + "> | | /en <" + "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" +); + +test_table!( + table_with_empty_margin, + Matrix::new(3, 3) + .insert((3, 2), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new(Cell::new(3, 2)).with(Span::column(2))) + .with(Margin::new(0, 0, 0, 0).fill('>', '<', 'V', '^')), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | https:// " + " | | www " + " | | . " + " | | redhat " + " | | .com " + " | | /en " +); + +#[test] +fn table_with_margin_and_min_width() { + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Cell::new(1, 1)).with(Span::column(2))) + .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')) + .with(Width::truncate(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "VVVVVVVVVVVVVVVVVVVV" + "> | co | co | col <" + ">--+----+----+-----<" + "> | 0-0 | 0-2 <" + "> | 1- | 1- | 1-2 <" + "> | 2- | 2- | 2-2 <" + "^^^^^^^^^^^^^^^^^^^^" + ) + ); + assert!(is_lines_equal(&table, 20)); +} + +#[test] +fn table_with_margin_and_max_width() { + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Cell::new(1, 1)).with(Span::column(2))) + .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')) + .with(Width::increase(50)) + .to_string(); + + assert_eq!(papergrid::util::string::string_width_multiline(&table), 50); + assert_eq!( + table, + static_table!( + "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" + "> N | column 0 | column 1 | column 2 <" + ">------+-------------+-------------+-------------<" + "> 0 | 0-0 | 0-2 <" + "> 1 | 1-0 | 1-1 | 1-2 <" + "> 2 | 2-0 | 2-1 | 2-2 <" + "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + ) + ); +} + +#[test] +#[ignore = "It's not yet clear what to do with such spans"] +fn table_0_spanned_with_width() { + let table = Matrix::table(0, 0) + .with(Modify::new(Cell::new(0, 0)).with(Span::column(0))) + .with(Width::increase(50)) + .to_string(); + + assert_eq!(table, "++\n|\n++\n"); + + let table = Matrix::table(0, 0) + .with(Modify::new(Cell::new(0, 0)).with(Span::column(0))) + .with(Width::truncate(50)) + .to_string(); + + assert_eq!(table, "++\n|\n++\n"); +} + +#[test] +fn margin_color_test_not_colored_feature() { + use tabled::settings::Color; + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Margin::new(2, 2, 2, 2).fill('>', '<', 'V', '^').colorize( + Color::BG_GREEN, + Color::BG_YELLOW, + Color::BG_RED, + Color::BG_BLUE, + )) + .to_string(); + + assert_eq!( + table, + static_table!( + "\u{1b}[41mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[49m" + "\u{1b}[41mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[49m" + "\u{1b}[42m>>\u{1b}[49m N | column 0 | column 1 | column 2 \u{1b}[43m<<\u{1b}[49m" + "\u{1b}[42m>>\u{1b}[49m---+----------+----------+----------\u{1b}[43m<<\u{1b}[49m" + "\u{1b}[42m>>\u{1b}[49m 0 | 0-0 | 0-1 | 0-2 \u{1b}[43m<<\u{1b}[49m" + "\u{1b}[42m>>\u{1b}[49m 1 | 1-0 | 1-1 | 1-2 \u{1b}[43m<<\u{1b}[49m" + "\u{1b}[42m>>\u{1b}[49m 2 | 2-0 | 2-1 | 2-2 \u{1b}[43m<<\u{1b}[49m" + "\u{1b}[44m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[49m" + "\u{1b}[44m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[49m" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn margin_color_test() { + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Margin::new(2, 2, 2, 2).fill('>', '<', 'V', '^').colorize( + Color::try_from(" ".red().bold().to_string()).unwrap(), + Color::try_from(" ".green().to_string()).unwrap(), + Color::try_from(" ".on_blue().red().bold().to_string()).unwrap(), + Color::try_from(" ".on_yellow().blue().to_string()).unwrap(), + )) + .to_string(); + + assert_eq!( + table, + static_table!( + "\u{1b}[1m\u{1b}[31m\u{1b}[44mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[22m\u{1b}[39m\u{1b}[49m" + "\u{1b}[1m\u{1b}[31m\u{1b}[44mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[22m\u{1b}[39m\u{1b}[49m" + "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m N | column 0 | column 1 | column 2 \u{1b}[32m<<\u{1b}[39m" + "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m---+----------+----------+----------\u{1b}[32m<<\u{1b}[39m" + "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m 0 | 0-0 | 0-1 | 0-2 \u{1b}[32m<<\u{1b}[39m" + "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m 1 | 1-0 | 1-1 | 1-2 \u{1b}[32m<<\u{1b}[39m" + "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m 2 | 2-0 | 2-1 | 2-2 \u{1b}[32m<<\u{1b}[39m" + "\u{1b}[34m\u{1b}[43m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[39m\u{1b}[49m" + "\u{1b}[34m\u{1b}[43m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[39m\u{1b}[49m" + ) + ); +} diff --git a/vendor/tabled/tests/settings/merge_test.rs b/vendor/tabled/tests/settings/merge_test.rs new file mode 100644 index 000000000..cbc119aa9 --- /dev/null +++ b/vendor/tabled/tests/settings/merge_test.rs @@ -0,0 +1,196 @@ +#![cfg(feature = "std")] + +use tabled::{settings::merge::Merge, Table}; + +use testing_table::test_table; + +test_table!( + merge_horizontal, + Table::new([[0, 1, 1], [1, 1, 2], [1, 1, 1]]).with(Merge::horizontal()), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 0 | 1 |" + "+---+---+---+" + "| 1 | 2 |" + "+---+---+---+" + "| 1 |" + "+---+---+---+" +); + +test_table!( + merge_horizontal_with_no_duplicates, + Table::new([[0, 1, 2], [0, 1, 2], [0, 1, 2]]).with(Merge::horizontal()), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" +); + +test_table!( + merge_horizontal_empty, + Table::new([[0usize; 0]]).with(Merge::horizontal()), + "" +); + +test_table!( + merge_vertical_0, + Table::new([[0, 3, 5], [0, 3, 3], [0, 2, 3]]).with(Merge::vertical()), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+ +---+---+" + "| | 3 | 5 |" + "+ + +---+" + "+ +---+ 3 +" + "| | 2 | |" + "+---+---+---+" +); + +test_table!( + merge_vertical_1, + Table::new([[0, 3, 2], [0, 3, 3], [0, 2, 3]]).with(Merge::vertical()), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+ +---+ +" + "+ + 3 +---+" + "+ +---+ 3 +" + "| | 2 | |" + "+---+---+---+" +); + +test_table!( + merge_vertical_with_no_duplicates, + Table::new([[5; 3], [15; 3], [115; 3]]).with(Merge::vertical()), + "+-----+-----+-----+" + "| 0 | 1 | 2 |" + "+-----+-----+-----+" + "| 5 | 5 | 5 |" + "+-----+-----+-----+" + "| 15 | 15 | 15 |" + "+-----+-----+-----+" + "| 115 | 115 | 115 |" + "+-----+-----+-----+" +); + +test_table!( + merge_vertical_empty, + Table::new([[0usize; 0]]).with(Merge::vertical()), + "" +); + +test_table!( + merge_horizontal_and_vertical_0, + Table::new([[3, 3, 5], [3, 7, 8], [9, 10, 11]]).with(Merge::horizontal()).with(Merge::vertical()), + "+---+----+----+" + "| 0 | 1 | 2 |" + "+---+----+----+" + "| 3 | 5 |" + "+---+----+----+" + "| 3 | 7 | 8 |" + "+---+----+----+" + "| 9 | 10 | 11 |" + "+---+----+----+" +); + +test_table!( + merge_horizontal_and_vertical_1, + Table::new([[0, 1, 1], [1, 1, 2], [1, 1, 1]]).with(Merge::horizontal()).with(Merge::vertical()), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+ +---+---+" + "| | 1 |" + "+---+---+---+" + "| 1 | 2 |" + "+---+---+---+" + "| 1 |" + "+---+---+---+" +); + +test_table!( + merge_horizontal_and_vertical_2, + Table::new([[3, 4, 5], [3, 3, 8], [3, 10, 11]]).with(Merge::horizontal()).with(Merge::vertical()), + "+---+----+----+" + "| 0 | 1 | 2 |" + "+---+----+----+" + "| 3 | 4 | 5 |" + "+---+----+----+" + "| 3 | 8 |" + "+---+----+----+" + "| 3 | 10 | 11 |" + "+---+----+----+" +); + +test_table!( + merge_horizontal_and_vertical_3, + Table::new([[3, 4, 5], [3, 3, 8], [3, 10, 11]]).with(Merge::vertical()).with(Merge::horizontal()), + "+---+----+----+" + "| 0 | 1 | 2 |" + "+---+----+----+" + "| 3 | 4 | 5 |" + "+ +----+----+" + "| | 3 | 8 |" + "+ +----+----+" + "| | 10 | 11 |" + "+---+----+----+" +); + +test_table!( + merge_horizontal_and_vertical_4, + Table::new([[3, 4, 5], [4, 4, 8], [3, 4, 11]]).with(Merge::vertical()).with(Merge::horizontal()), + "+---+---+----+" + "| 0 | 1 | 2 |" + "+---+---+----+" + "| 3 | 4 | 5 |" + "+---+ +----+" + "| 4 | | 8 |" + "+---+ +----+" + "| 3 | | 11 |" + "+---+---+----+" +); + +test_table!( + merge_horizontal_and_vertical_5, + Table::new([[3, 4, 4], [4, 4, 8], [3, 4, 11]]).with(Merge::vertical()).with(Merge::horizontal()), + "+---+---+----+" + "| 0 | 1 | 2 |" + "+---+---+----+" + "| 3 | 4 | 4 |" + "+---+ +----+" + "| 4 | | 8 |" + "+---+ +----+" + "| 3 | | 11 |" + "+---+---+----+" +); + +test_table!( + merge_horizontal_and_vertical_6, + Table::new([[4, 4, 4], [5, 4, 8], [3, 4, 11]]).with(Merge::vertical()).with(Merge::horizontal()), + "+---+---+----+" + "| 0 | 1 | 2 |" + "+---+---+----+" + "| 4 | 4 | 4 |" + "+---+ +----+" + "| 5 | | 8 |" + "+---+ +----+" + "| 3 | | 11 |" + "+---+---+----+" +); + +test_table!( + merge_horizontal_and_vertical_7, + Table::new([[0, 0, 0], [0, 0, 1], [2, 0, 0]]).with(Merge::horizontal()).with(Merge::vertical()), + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+---+" + "| 0 |" + "+---+---+---+" + "| 0 | 1 |" + "+---+---+---+" + "| 2 | 0 |" + "+---+---+---+" +); diff --git a/vendor/tabled/tests/settings/mod.rs b/vendor/tabled/tests/settings/mod.rs new file mode 100644 index 000000000..da76a5e14 --- /dev/null +++ b/vendor/tabled/tests/settings/mod.rs @@ -0,0 +1,23 @@ +mod alignment_test; +mod color_test; +mod colorization; +mod column_names_test; +mod concat_test; +mod disable_test; +mod duplicate_test; +mod extract_test; +mod format_test; +mod formatting_test; +mod height_test; +mod highlingt_test; +mod margin_test; +mod merge_test; +mod padding_test; +mod panel_test; +mod render_settings; +mod rotate_test; +mod shadow_test; +mod span_test; +mod split_test; +mod style_test; +mod width_test; diff --git a/vendor/tabled/tests/settings/padding_test.rs b/vendor/tabled/tests/settings/padding_test.rs new file mode 100644 index 000000000..9a3edb4aa --- /dev/null +++ b/vendor/tabled/tests/settings/padding_test.rs @@ -0,0 +1,138 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + object::{Rows, Segment}, + Alignment, Modify, Padding, Style, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +#[cfg(feature = "color")] +use ::{owo_colors::OwoColorize, std::convert::TryFrom, tabled::settings::Color}; + +test_table!( + padding, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Rows::new(1..)).with(Padding::new(1, 1, 0, 2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " | | | " + " | | | " + " 1 | 1-0 | 1-1 | 1-2 " + " | | | " + " | | | " + " 2 | 2-0 | 2-1 | 2-2 " + " | | | " + " | | | " +); + +test_table!( + padding_with_set_characters, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Padding::new(1, 2, 1, 1).fill('>', '<', 'V', '^'))), + "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" + ">N<<|>column 0<<|>column 1<<|>column 2<<" + "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" + "----+-----------+-----------+-----------" + "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" + ">0<<|> 0-0 <<|> 0-1 <<|> 0-2 <<" + "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" + "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" + ">1<<|> 1-0 <<|> 1-1 <<|> 1-2 <<" + "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" + "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" + ">2<<|> 2-0 <<|> 2-1 <<|> 2-2 <<" + "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" +); + +test_table!( + padding_with_set_characters_and_zero_ident, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Padding::zero().fill('>', '<', '^', 'V'))), + "N|column 0|column 1|column 2" + "-+--------+--------+--------" + "0| 0-0 | 0-1 | 0-2 " + "1| 1-0 | 1-1 | 1-2 " + "2| 2-0 | 2-1 | 2-2 " +); + +test_table!( + padding_multiline, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Rows::new(1..)).with(Padding::new(1, 1, 1, 1))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " | | | " + " 0 | 0-0 | 0-1 | 0-2 " + " | | | " + " | | | " + " 1 | 1-0 | 1-1 | 1-2 " + " | | | " + " | | | " + " 2 | 2-0 | 2-1 | 2-2 " + " | | | " +); + +test_table!( + padding_multiline_with_vertical_alignment, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::center()).with(Alignment::center_vertical())) + .with(Modify::new(Rows::new(1..)).with(Padding::new(1, 1, 1, 1))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " | | | " + " 0 | 0-0 | 0-1 | 0-2 " + " | | | " + " | | | " + " 1 | 1-0 | 1-1 | 1-2 " + " | | | " + " | | | " + " 2 | 2-0 | 2-1 | 2-2 " + " | | | " +); + +#[cfg(feature = "color")] +test_table!( + padding_color, + { + let padding = Padding::new(2, 2, 2, 2).colorize( + Color::try_from(' '.on_yellow().to_string()).unwrap(), + Color::try_from(' '.on_blue().to_string()).unwrap(), + Color::try_from(' '.on_red().to_string()).unwrap(), + Color::try_from(' '.on_green().to_string()).unwrap(), + ); + + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Rows::new(1..)).with(padding)) + }, + " N | column 0 | column 1 | column 2 \n-----+----------+----------+----------\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[43m \u{1b}[49m0\u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 0-0 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 0-1 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 0-2 \u{1b}[44m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[43m \u{1b}[49m1\u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 1-0 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 1-1 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 1-2 \u{1b}[44m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[43m \u{1b}[49m2\u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 2-0 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 2-1 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 2-2 \u{1b}[44m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m" +); + +test_table!( + padding_table, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Padding::new(1, 1, 0, 2)), + " N | column 0 | column 1 | column 2 " + " | | | " + " | | | " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " | | | " + " | | | " + " 1 | 1-0 | 1-1 | 1-2 " + " | | | " + " | | | " + " 2 | 2-0 | 2-1 | 2-2 " + " | | | " + " | | | " +); diff --git a/vendor/tabled/tests/settings/panel_test.rs b/vendor/tabled/tests/settings/panel_test.rs new file mode 100644 index 000000000..613fe1d0e --- /dev/null +++ b/vendor/tabled/tests/settings/panel_test.rs @@ -0,0 +1,337 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + object::{Cell, Object, Rows, Segment}, + style::{BorderSpanCorrection, HorizontalLine}, + Alignment, Border, Highlight, Modify, Panel, Span, Style, Width, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +test_table!( + panel_has_no_style_by_default, + Matrix::new(3, 3).with(Style::psql()).with(Panel::horizontal(0,"Linux Distributions")), + " Linux Distributions " + "---+----------+----------+----------" + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + highlight_panel_0, + Matrix::new(3, 3) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Style::psql()) + .with(Highlight::new(Cell::new(0, 0), Border::filled('#'))), + "##### " + "# Linux Distributions " + "#####----------+----------+----------" + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + highlight_panel_1, + Matrix::new(3, 3) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Style::psql()) + .with(Highlight::new(Cell::new(0, 0), Border::filled('#'))) + .with(Highlight::new(Cell::new(0, 1), Border::filled('#'))) + .with(Highlight::new(Cell::new(0, 2), Border::filled('#'))) + .with(Highlight::new(Cell::new(0, 3), Border::filled('#'))), + "######################################" + "# Linux Distributions #" + "######################################" + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + top_panel, + Matrix::new(3, 3) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::psql()), + " Linux Distributions " + "---+----------+----------+----------" + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + bottom_panel, + Matrix::new(3, 3) + .with(Panel::horizontal(4,"Linux Distributions")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::psql()), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + " Linux Distributions " +); + +test_table!( + inner_panel, + Matrix::new(3, 3) + .with(Panel::horizontal(2,"Linux Distributions")) + .with(Modify::new(Rows::new(2..)).with(Alignment::center())) + .with(Style::psql()), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " Linux Distributions " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + header, + Matrix::new(3, 3) + .with(Panel::header("Linux Distributions")) + .with(Style::psql()) + .with(Modify::new(Rows::new(0..1)).with(Alignment::center())), + " Linux Distributions " + "---+----------+----------+----------" + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + footer, + Matrix::new(3, 3) + .with(Panel::header("Linux Distributions")) + .with(Panel::footer("The end")) + .with(Style::psql()) + .with(Modify::new(Rows::first().and(Rows::last())).with(Alignment::center())), + " Linux Distributions " + "---+----------+----------+----------" + " N | column 0 | column 1 | column 2 " + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + " The end " +); + +test_table!( + panel_style_uses_most_left_and_right_cell_styles, + Matrix::iter([(0, 1)]).with(Panel::horizontal(0,"Numbers")).with(Style::modern()), + "┌─────┬─────┐" + "│ Numbers │" + "├─────┼─────┤" + "│ i32 │ i32 │" + "├─────┼─────┤" + "│ 0 │ 1 │" + "└─────┴─────┘" +); + +test_table!( + panel_style_change, + Matrix::iter([(0, 1)]) + .with(Panel::horizontal(0,"Numbers")) + .with(Style::modern().intersection_top('─').horizontals([HorizontalLine::new(1, Style::modern().get_horizontal()).intersection(Some('┬'))])) + .with(Modify::new(Cell::new(0, 0)).with(Alignment::center())), + "┌───────────┐" + "│ Numbers │" + "├─────┬─────┤" + "│ i32 │ i32 │" + "├─────┼─────┤" + "│ 0 │ 1 │" + "└─────┴─────┘" +); + +test_table!( + panel_style_uses_most_left_and_right_cell_styles_correct, + Matrix::iter([(0, 1)]) + .with(Panel::horizontal(0,"Numbers")) + .with(Style::modern()) + .with(BorderSpanCorrection), + "┌───────────┐" + "│ Numbers │" + "├─────┬─────┤" + "│ i32 │ i32 │" + "├─────┼─────┤" + "│ 0 │ 1 │" + "└─────┴─────┘" +); + +test_table!( + panel_style_change_correct, + Matrix::iter([(0, 1)]) + .with(Panel::horizontal(0,"Numbers")) + .with(Style::modern().intersection_top('─').horizontals([HorizontalLine::new(1, Style::modern().get_horizontal()).intersection(Some('┬'))])) + .with(BorderSpanCorrection) + .with(Modify::new(Cell::new(0, 0)).with(Alignment::center())), + "┌───────────┐" + "│ Numbers │" + "├───────────┤" // it's different because we use a top_intersection char by default when making style for `Panel`s. + "│ i32 │ i32 │" + "├─────┼─────┤" + "│ 0 │ 1 │" + "└─────┴─────┘" +); + +test_table!( + panel_in_single_column, + #[allow(clippy::needless_borrow)] + Matrix::iter(&[(0)]).with(Panel::horizontal(0,"Numbers")).with(Style::modern()), + "┌─────────┐" + "│ Numbers │" + "├─────────┤" + "│ i32 │" + "├─────────┤" + "│ 0 │" + "└─────────┘" +); + +test_table!( + panel_vertical_0, + Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")), + " Linux Distributions | N | column 0 | column 1 | column 2 " + " +---+----------+----------+----------" + " | 0 | 0-0 | 0-1 | 0-2 " + " | 1 | 1-0 | 1-1 | 1-2 " + " | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + panel_vertical_1, + Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(1,"Linux Distributions")), + " N | Linux Distributions | column 0 | column 1 | column 2 " + "---+ +----------+----------+----------" + " 0 | | 0-0 | 0-1 | 0-2 " + " 1 | | 1-0 | 1-1 | 1-2 " + " 2 | | 2-0 | 2-1 | 2-2 " +); + +test_table!( + panel_vertical_2, + Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(4,"Linux Distributions")), + " N | column 0 | column 1 | column 2 | Linux Distributions " + "---+----------+----------+----------+ " + " 0 | 0-0 | 0-1 | 0-2 | " + " 1 | 1-0 | 1-1 | 1-2 | " + " 2 | 2-0 | 2-1 | 2-2 | " +); + +test_table!( + panel_vertical_0_wrap, + Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")).with(Modify::new(Cell::new(0, 0)).with(Width::wrap(3))), + " Lin | N | column 0 | column 1 | column 2 " + " ux | | | | " + " Dis | | | | " + " tri +---+----------+----------+----------" + " but | 0 | 0-0 | 0-1 | 0-2 " + " ion | 1 | 1-0 | 1-1 | 1-2 " + " s | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + panel_vertical_0_wrap_0, + Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")).with(Modify::new(Cell::new(0, 0)).with(Width::wrap(0))), + " | N | column 0 | column 1 | column 2 " + " +---+----------+----------+----------" + " | 0 | 0-0 | 0-1 | 0-2 " + " | 1 | 1-0 | 1-1 | 1-2 " + " | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + panel_vertical_0_wrap_100, + Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")).with(Modify::new(Cell::new(0, 0)).with(Width::wrap(100))), + " Linux Distributions | N | column 0 | column 1 | column 2 " + " +---+----------+----------+----------" + " | 0 | 0-0 | 0-1 | 0-2 " + " | 1 | 1-0 | 1-1 | 1-2 " + " | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + panel_horizontal_set_0, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Panel::vertical(0,"asd")), + " asd | Linux Distributions " + " +---+----------+----------+----------" + " | N | column 0 | column 1 | column 2 " + " | 0 | 0-0 | 0-1 | 0-2 " + " | 1 | 1-0 | 1-1 | 1-2 " + " | 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + panel_horizontal_set_1, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Panel::vertical(0,"asd")) + .with(Panel::vertical(5,"asd")) + , + " asd | Linux Distributions | asd " + " +---+----------+----------+----------+ " + " | N | column 0 | column 1 | column 2 | " + " | 0 | 0-0 | 0-1 | 0-2 | " + " | 1 | 1-0 | 1-1 | 1-2 | " + " | 2 | 2-0 | 2-1 | 2-2 | " +); + +test_table!( + ignore_col_span_intersect_with_other_span, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Panel::vertical(0,"asd")) + .with(Panel::vertical(5,"zxc")) + .with(Modify::new((1, 3)).with(Span::column(3)).with("wwwww")), + " asd | Linux Distributions | zxc " + " +---+----------+-------+----------+ " + " | N | column 0 | wwwww | column 2 | " + " | 0 | 0-0 | 0-1 | 0-2 | " + " | 1 | 1-0 | 1-1 | 1-2 | " + " | 2 | 2-0 | 2-1 | 2-2 | " +); + +test_table!( + panel_horizontal_x_2, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Panel::horizontal(0,"Linux Distributions")) + .with(Panel::vertical(0,"asd")) + .with(Panel::vertical(5,"zxc")) + .with(Modify::new((1, 3)).with(Span::column(2)).with("wwwww")), + " asd | Linux Distributions | zxc " + " +---+----------+-----+-----+ " + " | N | column 0 | wwwww | " + " | 0 | 0-0 | 0-1 | 0-2 | " + " | 1 | 1-0 | 1-1 | 1-2 | " + " | 2 | 2-0 | 2-1 | 2-2 | " +); + +test_table!( + ignore_row_span_intersect_with_other_span, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Panel::horizontal(2,"Linux Distributions")) + .with(Panel::vertical(0,"asd")) + .with(Panel::vertical(5,"zxc")) + .with(Modify::new((0, 3)).with(Span::row(4)).with("xxxxx")), + " asd | N | column 0 | xxxxx | column 2 | zxc " + " +---+----------+-------+----------+ " + " | 0 | 0-0 | 0-1 | 0-2 | " + " | Linux Distributions | " + " | 1 | 1-0 | 1-1 | 1-2 | " + " | 2 | 2-0 | 2-1 | 2-2 | " +); diff --git a/vendor/tabled/tests/settings/render_settings.rs b/vendor/tabled/tests/settings/render_settings.rs new file mode 100644 index 000000000..f25a59a67 --- /dev/null +++ b/vendor/tabled/tests/settings/render_settings.rs @@ -0,0 +1,292 @@ +#![cfg(feature = "std")] + +use tabled::settings::{ + formatting::{AlignmentStrategy, TabSize, TrimStrategy}, + object::Segment, + Alignment, Modify, Span, Style, +}; + +use crate::matrix::{Matrix, MatrixList}; +use testing_table::test_table; + +#[cfg(feature = "color")] +use owo_colors::OwoColorize; + +test_table!( + alignment_per_line, + Matrix::iter(multiline_data1()) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::right()).with(AlignmentStrategy::PerLine)), + " N | column 0 | column 1 | column 2 " + "-----------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " asd | 1-0 | 1-1 | 1-2 " + " 21213123 | | | " + " | | | " + " asdasd | | | " + " | | | " + " | | | " + " 2 | 2-0 | https:// | 2-2 " + " | | www | " + " | | . | " + " | | redhat | " + " | | .com | " + " | | /en | " +); + +test_table!( + alignment_per_line_with_trim_0, + Matrix::iter(multiline_data1()) + .with(Style::psql()) + .with(Alignment::right()) + .with(AlignmentStrategy::PerLine) + .with(TrimStrategy::Horizontal), + " N | column 0 | column 1 | column 2 " + "-----------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " asd | 1-0 | 1-1 | 1-2 " + " 21213123 | | | " + " | | | " + " asdasd | | | " + " | | | " + " | | | " + " 2 | 2-0 | https:// | 2-2 " + " | | www | " + " | | . | " + " | | redhat | " + " | | .com | " + " | | /en | " +); + +test_table!( + alignment_per_line_with_trim_1, + Matrix::iter(multiline_data2()) + .with(Style::psql()) + .with(Modify::new(Segment::all()) + .with(Alignment::center_vertical()) + .with(Alignment::left()) + .with(AlignmentStrategy::PerLine) + .with(TrimStrategy::Both)), + " N | column 0 | column 1 | column 2 " + "-------------------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " | | | " + " | | | " + " | | | " + " asd | 1-0 | 1-1 | 1-2 " + " 21213123 asdasd | | | " + " | | | " + " | | | " + " | | | " + " | | https:// | " + " | | www | " + " 2 | 2-0 | . | 2-2 " + " | | redhat | " + " | | .com | " + " | | /en | " +); + +test_table!( + tab_isnot_handled_by_default_test, + Matrix::iter(tab_data1()).with(Style::psql()), + " N | column 0 | column 1 | column 2 " + "--------------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 123\t123\tasdasd | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | htt\tps:// | 2-2 " + " | | www | " + " | | . | " + " | | red\that | " + " | | .c\tom | " + " | | /en | " +); + +test_table!( + tab_size_test_0, + Matrix::iter(tab_data1()).with(Style::psql()).with(TabSize::new(4)), + " N | column 0 | column 1 | column 2 " + "----------------------+----------+--------------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 123 123 asdasd | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | htt ps:// | 2-2 " + " | | www | " + " | | . | " + " | | red hat | " + " | | .c om | " + " | | /en | " +); + +test_table!( + tab_size_test_1, + Matrix::iter(tab_data1()).with(Style::psql()).with(Modify::new(Segment::all()).with(Alignment::right())).with(TabSize::new(2)), + " N | column 0 | column 1 | column 2 " + "------------------+----------+------------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 123 123 asdasd | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | htt ps:// | 2-2 " + " | | www | " + " | | . | " + " | | red hat | " + " | | .c om | " + " | | /en | " +); + +test_table!( + tab_size_test_2, + Matrix::iter(tab_data1()).with(Style::psql()).with(Modify::new(Segment::all()).with(Alignment::right())).with(TabSize::new(0)), + " N | column 0 | column 1 | column 2 " + "--------------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 123123asdasd | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | https:// | 2-2 " + " | | www | " + " | | . | " + " | | redhat | " + " | | .com | " + " | | /en | " +); + +test_table!( + tab_size_span_test, + Matrix::iter(tab_data2()) + .with(TabSize::new(4)) + .with(Style::psql()) + .with(Modify::new((0, 0)).with(Span::column(3))) + .with(Modify::new((1, 0)).with(Span::column(2))) + .with(Modify::new((2, 1)).with(Span::column(2))), + " N | column 2 " + "----------------------+-----+--------------+----------" + " H ello World | 0-1 | 0-2 " + " 123 123 asdasd | 1-0 | 1-2 " + " 2 | 2-0 | htt ps:// | 2-2 " + " | | www | " + " | | . | " + " | | red hat | " + " | | .c om | " + " | | /en | " +); + +test_table!( + test_top_alignment_and_vertical_trim_1, + Matrix::iter(vec![" \n\n\n Hello World"]) + .with(Style::modern()) + .with(Modify::new(Segment::all()).with(Alignment::top()).with(TrimStrategy::Vertical)), + "┌─────────────────┐" + "│ &str │" + "├─────────────────┤" + "│ Hello World │" + "│ │" + "│ │" + "│ │" + "└─────────────────┘" +); + +#[cfg(feature = "color")] +test_table!( + trim_colored_string_test_2, + Matrix::iter(colored_data()) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::right()).with(TrimStrategy::None)), + " N | column 0 | column 1 | column 2 " + "-----------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " \u{1b}[31masd\u{1b}[39m | 1-0 | 1-1 | 1-2 " + " \u{1b}[31m21213123\u{1b}[39m | | | " + " | | | " + " \u{1b}[31m asdasd\u{1b}[39m | | | " + " | | | " + " \u{1b}[31m\u{1b}[39m | | | " + " 2 | 2-0 | \u{1b}[44mhttps://\u{1b}[49m | 2-2 " + " | | \u{1b}[44mwww\u{1b}[49m | " + " | | \u{1b}[44m.\u{1b}[49m | " + " | | \u{1b}[44mredhat\u{1b}[49m | " + " | | \u{1b}[44m.com\u{1b}[49m | " + " | | \u{1b}[44m/en\u{1b}[49m | " +); + +#[cfg(feature = "color")] +test_table!( + trim_colored_string_test_1, + Matrix::iter(colored_data()) + .with(Style::psql()) + .with( + Modify::new(Segment::all()) + .with(Alignment::right()) + .with(TrimStrategy::Horizontal) + .with(AlignmentStrategy::PerLine), + ), + " N | column 0 | column 1 | column 2 " + "-----------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " \u{1b}[31masd\u{1b}[39m | 1-0 | 1-1 | 1-2 " + " \u{1b}[31m21213123\u{1b}[39m | | | " + " | | | " + " \u{1b}[31masdasd\u{1b}[39m | | | " + " | | | " + " \u{1b}[31m\u{1b}[39m | | | " + " 2 | 2-0 | \u{1b}[44mhttps://\u{1b}[49m | 2-2 " + " | | \u{1b}[44mwww\u{1b}[49m | " + " | | \u{1b}[44m.\u{1b}[49m | " + " | | \u{1b}[44mredhat\u{1b}[49m | " + " | | \u{1b}[44m.com\u{1b}[49m | " + " | | \u{1b}[44m/en\u{1b}[49m | " +); +#[cfg(feature = "color")] +test_table!( + trim_colored_string_test_0, + Matrix::iter(colored_data()) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::right()).with(TrimStrategy::Horizontal)), + " N | column 0 | column 1 | column 2 " + "-----------+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " \u{1b}[31masd\u{1b}[39m | 1-0 | 1-1 | 1-2 " + " \u{1b}[31m21213123\u{1b}[39m | | | " + " | | | " + " \u{1b}[31masdasd\u{1b}[39m | | | " + " | | | " + " \u{1b}[31m\u{1b}[39m | | | " + " 2 | 2-0 | \u{1b}[44mhttps://\u{1b}[49m | 2-2 " + " | | \u{1b}[44mwww\u{1b}[49m | " + " | | \u{1b}[44m.\u{1b}[49m | " + " | | \u{1b}[44mredhat\u{1b}[49m | " + " | | \u{1b}[44m.com\u{1b}[49m | " + " | | \u{1b}[44m/en\u{1b}[49m | " +); + +fn multiline_data1() -> Vec<MatrixList<3, true>> { + let mut data = Matrix::list::<3, 3>(); + data[1][0] = String::from("asd\n21213123\n\n asdasd\n\n"); + data[2][2] = String::from("https://\nwww\n.\nredhat\n.com\n/en"); + data +} + +fn multiline_data2() -> Vec<MatrixList<3, true>> { + let mut data = Matrix::list::<3, 3>(); + data[1][0] = String::from("\n\n\nasd\n21213123 asdasd\n\n\n"); + data[2][2] = String::from("https://\nwww\n.\nredhat\n.com\n/en"); + data +} + +fn tab_data1() -> Vec<MatrixList<3, true>> { + let mut data = Matrix::list::<3, 3>(); + data[1][0] = String::from("123\t123\tasdasd"); + data[2][2] = String::from("htt\tps://\nwww\n.\nred\that\n.c\tom\n/en"); + data +} + +fn tab_data2() -> Vec<MatrixList<3, true>> { + let mut data = Matrix::list::<3, 3>(); + data[0][0] = String::from("\tH\t\tello\tWorld"); + data[1][0] = String::from("123\t123\tasdasd"); + data[2][2] = String::from("htt\tps://\nwww\n.\nred\that\n.c\tom\n/en"); + data +} + +#[cfg(feature = "color")] +fn colored_data() -> Vec<MatrixList<3, true>> { + let mut data = Matrix::list::<3, 3>(); + data[1][0] = "asd\n21213123\n\n asdasd\n\n".red().to_string(); + data[2][2] = "https://\nwww\n.\nredhat\n.com\n/en".on_blue().to_string(); + data +} diff --git a/vendor/tabled/tests/settings/rotate_test.rs b/vendor/tabled/tests/settings/rotate_test.rs new file mode 100644 index 000000000..42a781818 --- /dev/null +++ b/vendor/tabled/tests/settings/rotate_test.rs @@ -0,0 +1,214 @@ +#![cfg(feature = "std")] + +// todo: add method for SPACING between cells. + +use tabled::settings::{ + object::{Cell, Rows}, + Border, Highlight, Rotate, +}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +#[test] +fn test_rotate() { + let table = || Matrix::iter([(123, 456, 789), (234, 567, 891)]); + + assert_eq!( + table() + .with(Rotate::Left) + .with(Rotate::Left) + .with(Rotate::Left) + .with(Rotate::Left) + .to_string(), + table().to_string() + ); + assert_eq!( + table() + .with(Rotate::Right) + .with(Rotate::Right) + .with(Rotate::Right) + .with(Rotate::Right) + .to_string(), + table().to_string() + ); + assert_eq!( + table().with(Rotate::Right).with(Rotate::Left).to_string(), + table().to_string() + ); + assert_eq!( + table().with(Rotate::Left).with(Rotate::Right).to_string(), + table().to_string() + ); + assert_eq!( + table().with(Rotate::Bottom).with(Rotate::Top).to_string(), + table().to_string() + ); + assert_eq!( + table() + .with(Rotate::Bottom) + .with(Rotate::Bottom) + .to_string(), + table().to_string() + ); + assert_eq!( + table().with(Rotate::Top).with(Rotate::Top).to_string(), + table().to_string() + ); +} + +test_table!( + test_3x3_box_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Rotate::Left), + "+-----+-----+-----+" + "| i32 | 789 | 891 |" + "+-----+-----+-----+" + "| i32 | 456 | 567 |" + "+-----+-----+-----+" + "| i32 | 123 | 234 |" + "+-----+-----+-----+" +); + +test_table!( + test_3x3_box_1, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Rotate::Left).with(Rotate::Right).with(Rotate::Right), + "+-----+-----+-----+" + "| 234 | 123 | i32 |" + "+-----+-----+-----+" + "| 567 | 456 | i32 |" + "+-----+-----+-----+" + "| 891 | 789 | i32 |" + "+-----+-----+-----+" +); + +test_table!( + test_left_rotate, + Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Left), + "+-----+-----+-----+-----+" + "| i32 | 789 | 891 | 333 |" + "+-----+-----+-----+-----+" + "| i32 | 456 | 567 | 222 |" + "+-----+-----+-----+-----+" + "| i32 | 123 | 234 | 111 |" + "+-----+-----+-----+-----+" +); + +test_table!( + test_right_rotate, + Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Right), + "+-----+-----+-----+-----+" + "| 111 | 234 | 123 | i32 |" + "+-----+-----+-----+-----+" + "| 222 | 567 | 456 | i32 |" + "+-----+-----+-----+-----+" + "| 333 | 891 | 789 | i32 |" + "+-----+-----+-----+-----+" +); + +test_table!( + test_bottom_rotate, + Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Bottom), + "+-----+-----+-----+" + "| 111 | 222 | 333 |" + "+-----+-----+-----+" + "| 234 | 567 | 891 |" + "+-----+-----+-----+" + "| 123 | 456 | 789 |" + "+-----+-----+-----+" + "| i32 | i32 | i32 |" + "+-----+-----+-----+" +); + +test_table!( + test_top_rotate, + Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Top), + "+-----+-----+-----+" + "| 111 | 222 | 333 |" + "+-----+-----+-----+" + "| 234 | 567 | 891 |" + "+-----+-----+-----+" + "| 123 | 456 | 789 |" + "+-----+-----+-----+" + "| i32 | i32 | i32 |" + "+-----+-----+-----+" +); + +test_table!( + rotate_preserve_border_styles_test_0, + Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]) + .with(Highlight::new(Rows::single(0), Border::default().top('*'))) + .with(Rotate::Left), + "+*****************+-----+" + "| i32 | 789 | 891 | 333 |" + "+-----+-----+-----+-----+" + "| i32 | 456 | 567 | 222 |" + "+-----+-----+-----+-----+" + "| i32 | 123 | 234 | 111 |" + "+-----+-----+-----+-----+" +); + +// it's a correct behaviour because +// when we sen bottom border of cell(0, 2) we also set top border of cell(1, 2) +// +// todo: determine if it's correct +test_table!( + rotate_preserve_border_styles_test_1, + Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]) + .with(Highlight::new(Cell::new(0, 2), Border::default().bottom('*'))) + .with(Rotate::Left), + "+-----+-----+-----+-----+" + "| i32 | 789 | 891 | 333 |" + "+-----+-----+*****+-----+" + "| i32 | 456 | 567 | 222 |" + "+-----+-----+-----+-----+" + "| i32 | 123 | 234 | 111 |" + "+-----+-----+-----+-----+" +); + +test_table!( + test_left_rotate_1, + Matrix::iter([(0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 4, 5)]).with(Rotate::Left), + "+-----+---+---+" + "| i32 | 5 | 5 |" + "+-----+---+---+" + "| i32 | 4 | 4 |" + "+-----+---+---+" + "| i32 | 3 | 3 |" + "+-----+---+---+" + "| i32 | 2 | 2 |" + "+-----+---+---+" + "| i32 | 1 | 1 |" + "+-----+---+---+" + "| i32 | 0 | 0 |" + "+-----+---+---+" +); + +test_table!( + test_right_rotate_1, + Matrix::iter([(0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 4, 5)]).with(Rotate::Right), + "+---+---+-----+" + "| 0 | 0 | i32 |" + "+---+---+-----+" + "| 1 | 1 | i32 |" + "+---+---+-----+" + "| 2 | 2 | i32 |" + "+---+---+-----+" + "| 3 | 3 | i32 |" + "+---+---+-----+" + "| 4 | 4 | i32 |" + "+---+---+-----+" + "| 5 | 5 | i32 |" + "+---+---+-----+" +); + +test_table!( + test_bottom_rotate_1, + Matrix::iter([(0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 4, 5)]).with(Rotate::Bottom), + "+-----+-----+-----+-----+-----+-----+" + "| 0 | 1 | 2 | 3 | 4 | 5 |" + "+-----+-----+-----+-----+-----+-----+" + "| 0 | 1 | 2 | 3 | 4 | 5 |" + "+-----+-----+-----+-----+-----+-----+" + "| i32 | i32 | i32 | i32 | i32 | i32 |" + "+-----+-----+-----+-----+-----+-----+" +); diff --git a/vendor/tabled/tests/settings/shadow_test.rs b/vendor/tabled/tests/settings/shadow_test.rs new file mode 100644 index 000000000..2d169698c --- /dev/null +++ b/vendor/tabled/tests/settings/shadow_test.rs @@ -0,0 +1,105 @@ +#![cfg(feature = "std")] + +use tabled::settings::{Shadow, Style}; + +use crate::matrix::Matrix; +use testing_table::test_table; + +#[cfg(feature = "color")] +use ::{owo_colors::OwoColorize, std::convert::TryFrom, tabled::settings::Color}; + +test_table!( + test_shadow_bottom_right_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1)), + " i32 | i32 | i32 " + "-----+-----+-----▒" + " 123 | 456 | 789 ▒" + " 234 | 567 | 891 ▒" + " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" +); + +test_table!( + test_shadow_bottom_left_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1).set_left()), + " i32 | i32 | i32 " + "▒-----+-----+-----" + "▒ 123 | 456 | 789 " + "▒ 234 | 567 | 891 " + "▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ " +); + +test_table!( + test_shadow_top_right_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1).set_top()), + " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" + " i32 | i32 | i32 ▒" + "-----+-----+-----▒" + " 123 | 456 | 789 ▒" + " 234 | 567 | 891 " +); + +test_table!( + test_shadow_top_left_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1).set_top().set_left()), + "▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ " + "▒ i32 | i32 | i32 " + "▒-----+-----+-----" + "▒ 123 | 456 | 789 " + " 234 | 567 | 891 " +); + +test_table!( + test_shadow_set_fill, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(1).set_fill('▓')), + "+-----+-----+-----+ " + "| i32 | i32 | i32 |▓" + "+-----+-----+-----+▓" + "| 123 | 456 | 789 |▓" + "+-----+-----+-----+▓" + "| 234 | 567 | 891 |▓" + "+-----+-----+-----+▓" + " ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓" +); + +test_table!( + test_shadow_size_1, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(2).set_fill('▓')), + "+-----+-----+-----+ " + "| i32 | i32 | i32 |▓▓" + "+-----+-----+-----+▓▓" + "| 123 | 456 | 789 |▓▓" + "+-----+-----+-----+▓▓" + "| 234 | 567 | 891 |▓▓" + "+-----+-----+-----+▓▓" + " ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓" + " ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓" +); + +test_table!( + test_shadow_set_offset_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(2).set_offset(3)), + "+-----+-----+-----+ " + "| i32 | i32 | i32 | " + "+-----+-----+-----+ " + "| 123 | 456 | 789 |▒▒" + "+-----+-----+-----+▒▒" + "| 234 | 567 | 891 |▒▒" + "+-----+-----+-----+▒▒" + " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" + " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" +); + +#[cfg(feature = "color")] +test_table!( + test_shadow_set_color_0, + Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(2).set_offset(3).set_color(Color::try_from(' '.red().to_string()).unwrap())), + "+-----+-----+-----+ " + "| i32 | i32 | i32 | " + "+-----+-----+-----+ " + "| 123 | 456 | 789 |\u{1b}[31m▒▒\u{1b}[39m" + "+-----+-----+-----+\u{1b}[31m▒▒\u{1b}[39m" + "| 234 | 567 | 891 |\u{1b}[31m▒▒\u{1b}[39m" + "+-----+-----+-----+\u{1b}[31m▒▒\u{1b}[39m" + " \u{1b}[31m▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\u{1b}[39m" + " \u{1b}[31m▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\u{1b}[39m" +); diff --git a/vendor/tabled/tests/settings/span_test.rs b/vendor/tabled/tests/settings/span_test.rs new file mode 100644 index 000000000..ed61d8619 --- /dev/null +++ b/vendor/tabled/tests/settings/span_test.rs @@ -0,0 +1,1234 @@ +#![cfg(feature = "std")] +#![allow(clippy::redundant_clone)] + +use std::iter::FromIterator; + +use tabled::{ + builder::Builder, + grid::config::Position, + settings::{ + object::{Columns, Segment}, + style::{Border, BorderSpanCorrection, Style}, + Alignment, Highlight, Modify, Padding, Panel, Span, + }, + Table, +}; + +use crate::matrix::Matrix; +use testing_table::{static_table, test_table}; + +test_table!( + span_column_test_0, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Columns::single(0)).with(Span::column(2))), + " N | column 1 | column 2 " + "-+-+----------+----------" + " 0 | 0-1 | 0-2 " + " 1 | 1-1 | 1-2 " + " 2 | 2-1 | 2-2 " +); + +test_table!( + span_column_test_1, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Columns::new(1..2)).with(Span::column(2))), + " N | column 0 | column 2 " + "---+-----+----+----------" + " 0 | 0-0 | 0-2 " + " 1 | 1-0 | 1-2 " + " 2 | 2-0 | 2-2 " +); + +test_table!( + span_column_test_2, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Columns::single(0)).with(Span::column(4))), + " N " + "+++" + " 0 " + " 1 " + " 2 " +); + +test_table!( + cell_span_test_0, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((0, 0)).with(Span::column(2))), + " N | column 1 | column 2 " + "---+-----+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_1, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 0)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_2, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((2, 0)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_3, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((3, 0)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_4, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((0, 1)).with(Span::column(2))), + " N | column 0 | column 2 " + "---+-----+-----+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_5, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 1)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_6, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((2, 1)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_7, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((3, 1)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-2 " +); + +test_table!( + cell_span_test_8, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 2)).with(Span::column(2))), + " N | column 0 | column 1 " + "---+----------+-----+-----" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_9, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((1, 2)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_10, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((2, 2)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + cell_span_test_11, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((3, 2)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 " +); + +test_table!( + span_multiline, + Matrix::new(3, 3) + .insert((3, 2), "https://\nwww\n.\nredhat\n.com\n/en") + .with(Style::psql()) + .with(Modify::new((3, 2)).with(Span::column(2))), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | https:// " + " | | www " + " | | . " + " | | redhat " + " | | .com " + " | | /en " +); + +test_table!( + indent_works_in_spaned_columns, + Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Padding::new(3, 0, 0, 0))) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 1)).with(Span::column(3))) + .with(Modify::new((3, 1)).with(Span::column(3))), + " N| column 0| column 1| column 2" + "----+-----------+-----------+-----------" + " 0| 0-0 " + " 1| 1-0 | 1-1 | 1-2 " + " 2| 2-0 " +); + +test_table!( + spaned_columns_with_collision, + Matrix::iter([["just 1 column"; 5]; 5]) + .with(Style::modern()) + .with( + Modify::new((0, 0)) + .with(Span::column(5)) + .with("span all 5 columns"), + ) + .with( + Modify::new((1, 0)) + .with(Span::column(4)) + .with("span 4 columns"), + ) + .with( + Modify::new((2, 0)) + .with(Span::column(3)) + .with("span 3 columns"), + ) + .with( + Modify::new((2, 3)) + .with(Span::column(2)) + .with("span 2 columns"), + ) + .with( + Modify::new((3, 0)) + .with(Span::column(2)) + .with("span 3 columns"), + ) + .with( + Modify::new((3, 2)) + .with(Span::column(3)) + .with("span 3 columns"), + ) + .with( + Modify::new((4, 1)) + .with(Span::column(4)) + .with("span 4 columns"), + ), + "┌───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐" + "│ span all 5 columns │" + "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" + "│ span 4 columns │ just 1 column │" + "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" + "│ span 3 columns │ span 2 columns │" + "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" + "│ span 3 columns │ span 3 columns │" + "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" + "│ just 1 column │ span 4 columns │" + "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" + "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ just 1 column │" + "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘" +); + +test_table!( + span_with_panel_test_0, + Matrix::iter([[1, 2, 3]]) + .with(Panel::horizontal(0,"Tabled Releases")) + .with(Modify::new((1, 0)).with(Span::column(2))) + .with(Style::ascii()), + "+-----+-----+-----+" + "| Tabled Releases |" + "+-----+-----+-----+" + "| 0 | 2 |" + "+-----+-----+-----+" + "| 1 | 2 | 3 |" + "+-----+-----+-----+" +); + +test_table!( + span_with_panel_test_1, + Matrix::iter([[1, 2, 3], [4, 5, 6]]) + .with(Panel::horizontal(0,"Tabled Releases")) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(Style::ascii()), + "+-----+-----+-----+" + "| Tabled Releases |" + "+-----+-----+-----+" + "| 0 | 1 | 2 |" + "+-----+-----+-----+" + "| 1 | 3 |" + "+-----+-----+-----+" + "| 4 | 5 | 6 |" + "+-----+-----+-----+" +); + +test_table!( + span_with_panel_test_2, + Matrix::iter([[1, 2, 3], [4, 5, 6]]) + .with(Panel::horizontal(0,"Tabled Releases")) + .with(Modify::new((1, 0)).with(Span::column(2))) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(Style::ascii()), + "+-----+-----+-----+" + "| Tabled Releases |" + "+-----+-----+-----+" + "| 0 | 2 |" + "+-----+-----+-----+" + "| 1 | 3 |" + "+-----+-----+-----+" + "| 4 | 5 | 6 |" + "+-----+-----+-----+" +); + +test_table!( + span_with_panel_with_correction_test_0, + Matrix::iter([[1, 2, 3]]) + .with(Panel::horizontal(0,"Tabled Releases")) + .with(Modify::new((1, 0)).with(Span::column(2))) + .with(Style::ascii()) + .with(BorderSpanCorrection), + "+-----------------+" + "| Tabled Releases |" + "+-----------+-----+" + "| 0 | 2 |" + "+-----+-----+-----+" + "| 1 | 2 | 3 |" + "+-----+-----+-----+" +); + +test_table!( + span_with_panel_with_correction_test_1, + Matrix::iter([[1, 2, 3], [4, 5, 6]]) + .with(Panel::horizontal(0,"Tabled Releases")) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(Style::ascii()) + .with(BorderSpanCorrection), + "+-----------------+" + "| Tabled Releases |" + "+-----+-----+-----+" + "| 0 | 1 | 2 |" + "+-----+-----+-----+" + "| 1 | 3 |" + "+-----+-----+-----+" + "| 4 | 5 | 6 |" + "+-----+-----+-----+" +); + +test_table!( + span_with_panel_with_correction_test_2, + Matrix::iter([[1, 2, 3], [4, 5, 6]]) + .with(Panel::horizontal(0,"Tabled Releases")) + .with(Modify::new((1, 0)).with(Span::column(2))) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(Style::ascii()) + .with(BorderSpanCorrection), + "+-----------------+" + "| Tabled Releases |" + "+-----------+-----+" + "| 0 | 2 |" + "+-----------+-----+" + "| 1 | 3 |" + "+-----+-----+-----+" + "| 4 | 5 | 6 |" + "+-----+-----+-----+" +); + +#[test] +#[should_panic] +#[ignore = "span zero not yet decided"] +fn span_column_exceeds_boundaries_test() { + // todo: determine if it's the right behaiviour + + Matrix::new(3, 3) + .with(Modify::new(Columns::single(0)).with(Span::column(100))) + .to_string(); +} + +#[test] +#[ignore = "span zero not yet decided"] +fn span_cell_exceeds_boundaries_test() { + // these tests shows that exiding boundaries causes invalid behaiviour + // + // todo: determine if it's the right behaiviour + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((0, 0)).with(Span::column(20))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N " + "---+-----+-----+-----" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 1)).with(Span::column(20))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 0)).with(Span::column(20))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); +} + +#[test] +#[ignore = "span zero not yet decided"] +fn span_zero_test() { + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 0)).with(Span::column(0))) + .to_string(); + + assert_eq!( + table, + static_table!( + " column 0 | column 1 | column 2 " + "----+-----+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 1)).with(Span::column(0))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 1 | column 2 " + "---+-----+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 2)).with(Span::column(0))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 2 " + "---+-----+-----+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 3)).with(Span::column(0))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 " + "---+----------+-----+-----" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 4)).with(Span::column(0))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::psql()) + .with(Modify::new((0, 0)).with(Span::column(0))) + .with(Modify::new((1, 1)).with(Span::column(0))) + .with(Modify::new((2, 2)).with(Span::column(0))) + .with(Modify::new((3, 2)).with(Span::column(0))) + .with(Modify::new((3, 1)).with(Span::column(0))) + .to_string(); + + assert_eq!( + table, + static_table!( + " column 0 | column 1 | column 2 " + "------+-------+------+----------" + " 0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-2 " + " 2 | 2-2 " + ) + ); +} + +#[test] +#[ignore = "span zero not yet decided"] +fn span_all_table_to_zero_test() { + let table = Matrix::table(2, 2) + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Span::column(0))) + .to_string(); + + // todo: determine whether it's correct + assert_eq!(table, static_table!("\n++\n\n\n")); +} + +mod row { + use tabled::settings::object::Rows; + + use super::*; + + #[test] + fn span_row_test() { + let table = Matrix::new(3, 3); + { + let table_str = table + .clone() + .with(Style::ascii()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Rows::single(0)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table_str, + static_table!( + "+---+----------+----------+----------+" + "+ N + column 0 + column 1 + column 2 +" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" + ) + ); + + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Rows::single(0)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N + column 0 + column 1 + column 2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Rows::new(1..2)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Rows::single(0)).with(Span::row(4))) + .to_string(); + + assert_eq!(table, " N + column 0 + column 1 + column 2 "); + } + } + + #[test] + fn cell_span_test() { + let table = Matrix::new(3, 3); + { + // first column cells row span = 2 + + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((0, 0)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + " +----------+----------+----------" + " | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 0)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new((2, 0)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " | 2-0 | 2-1 | 2-2 " + ) + ); + } + } + + { + // first row cells row span = 2 + + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((0, 1)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+ +----------+----------" + " 0 | | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((0, 2)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+ +----------" + " 0 | 0-0 | | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new((0, 3)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+ " + " 0 | 0-0 | 0-1 | " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + } + + { + // second column span=2 + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 1)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((2, 1)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | | 2-1 | 2-2 " + ) + ); + } + } + { + // 3rd column span=2 + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 2)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((2, 2)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | | 2-2 " + ) + ); + } + } + { + // 4th column span=2 + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((1, 3)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + } + { + let table = table + .clone() + .with(Style::psql()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new((2, 3)).with(Span::row(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | " + ) + ); + } + } + } + + #[test] + fn span_with_panel_with_correction_test() { + let data = [[1, 2, 3]]; + let table = Table::new(data) + .with(Modify::new((0, 0)).with(Span::row(2))) + .with(Style::ascii()) + .with(BorderSpanCorrection) + .to_string(); + + assert_eq!( + table, + static_table!( + "+---+---+---+" + "| 0 | 1 | 2 |" + "| +---+---+" + "| | 2 | 3 |" + "+---+---+---+" + ) + ); + + let data = [[1, 2, 3], [4, 5, 6]]; + let table = Table::new(data) + .with(Modify::new((1, 0)).with(Span::row(2))) + .with(Modify::new((0, 2)).with(Span::row(3))) + .with(Style::ascii()) + .with(BorderSpanCorrection) + .to_string(); + + assert_eq!( + table, + static_table!( + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+---+ |" + "| 1 | 2 | |" + "| +---+ |" + "| | 5 | |" + "+---+---+---+" + ) + ); + + let data = [[1, 2, 3], [4, 5, 6]]; + let table = Table::new(data) + .with(Modify::new((1, 0)).with(Span::row(2))) + .with(Modify::new((0, 2)).with(Span::row(3))) + .with(Modify::new((0, 1)).with(Span::row(2))) + .with(Style::ascii()) + .with(BorderSpanCorrection) + .to_string(); + + assert_eq!( + table, + static_table!( + "+---+---+---+" + "| 0 | 1 | 2 |" + "+---+ | |" + "| 1 +---+ |" + "| | 5 | |" + "+---+---+---+" + ) + ); + + let data = [[1, 2, 3], [4, 5, 6]]; + let table = Table::new(data) + .with(Modify::new((1, 0)).with(Span::row(2))) + .with(Modify::new((0, 1)).with(Span::row(2)).with(Span::column(2))) + .with(Style::ascii()) + .with(BorderSpanCorrection) + .to_string(); + + assert_eq!( + table, + static_table!( + "+---+-------+" + "| 0 | 1 |" + "+---+ +" + "| 1 +---+---+" + "| | 5 | 6 |" + "+---+---+---+" + ) + ); + } + + #[test] + fn span_example_test() { + let data = [["just 1 column"; 5]; 5]; + + let h_span = |r, c, span| Modify::new((r, c)).with(Span::column(span)); + let v_span = |r, c, span| Modify::new((r, c)).with(Span::row(span)); + + let table = Table::new(data) + .with(h_span(0, 0, 5).with(String::from("span all 5 columns"))) + .with(h_span(1, 0, 4).with(String::from("span 4 columns"))) + .with(h_span(2, 0, 2).with(String::from("span 2 columns"))) + .with(v_span(2, 4, 4).with(String::from("just 1 column\nspan\n4\ncolumns"))) + .with(v_span(3, 1, 2).with(String::from("span 2 columns\nspan\n2\ncolumns"))) + .with(v_span(2, 3, 3).with(String::from("just 1 column\nspan\n3\ncolumns"))) + .with(h_span(3, 1, 2)) + .with(Style::modern()) + .with(BorderSpanCorrection) + .with(Modify::new(Segment::all()).with(Alignment::center_vertical())) + .to_string(); + + assert_eq!( + table, + static_table!( + "┌───────────────────────────────────────────────────────────────────────────────┐" + "│ span all 5 columns │" + "├───────────────────────────────────────────────────────────────┬───────────────┤" + "│ span 4 columns │ just 1 column │" + "├───────────────────────────────┬───────────────┬───────────────┼───────────────┤" + "│ span 2 columns │ just 1 column │ │ │" + "├───────────────┬───────────────┴───────────────┤ just 1 column │ │" + "│ just 1 column │ span 2 columns │ span │ just 1 column │" + "│ │ span │ 3 │ span │" + "├───────────────┤ 2 │ columns │ 4 │" + "│ just 1 column │ columns │ │ columns │" + "├───────────────┼───────────────┬───────────────┼───────────────┤ │" + "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ │" + "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘" + ) + ) + } + + #[test] + fn highlight_row_span_test() { + let data = [ + ["1", "2\n2\n2\n2\n2\n2\n2\n2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], + ]; + let table = Table::new(data) + .with(Modify::new((1, 1)).with(Span::row(3))) + .with(Style::modern()) + .with(Highlight::new(Columns::single(1), Border::filled('*'))) + .to_string(); + + assert_eq!( + table, + static_table!( + "┌───*****───┐" + "│ 0 * 1 * 2 │" + "├───*───*───┤" + "│ 1 * 2 * 3 │" + "│ * 2 * │" + "├───* 2 *───┤" + "│ 4 * 2 * 6 │" + "│ * 2 * │" + "├───* 2 *───┤" + "│ 7 * 2 * 9 │" + "│ * 2 * │" + "└───*****───┘" + ) + ); + } +} + +#[test] +fn highlight_row_col_span_test() { + let data = [ + ["1", "2\n2\n2\n2\n2\n2\n2\n2", "3", "0"], + ["4", "5", "6", "0"], + ["7", "8", "9", "0"], + ]; + let table = Table::new(data) + .with(Modify::new((1, 1)).with(Span::row(3)).with(Span::column(2))) + .with(Style::modern()) + .with(Highlight::new(Columns::new(1..3), Border::filled('*'))) + .to_string(); + + assert_eq!( + table, + static_table!( + "┌───*********───┐" + "│ 0 * 1 │ 2 * 3 │" + "├───*───┼───*───┤" + "│ 1 * 2 * 0 │" + "│ * 2 * │" + "├───* 2 *───┤" + "│ 4 * 2 * 0 │" + "│ * 2 * │" + "├───* 2 *───┤" + "│ 7 * 2 * 0 │" + "│ * 2 * │" + "└───*********───┘" + ) + ); +} + +test_table!( + column_span_bigger_then_max, + Matrix::new(3, 3).with(Modify::new((0, 0)).with(Span::column(100))), + "+---+-----+-----+-----+" + "| N |" + "+---+-----+-----+-----+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+-----+-----+-----+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+-----+-----+-----+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+-----+-----+-----+" +); + +test_table!( + row_span_bigger_then_max, + Matrix::new(3, 3).with(Modify::new((0, 0)).with(Span::row(100))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+ +----------+----------+----------+" + "| | 0-0 | 0-1 | 0-2 |" + "+ +----------+----------+----------+" + "| | 1-0 | 1-1 | 1-2 |" + "+ +----------+----------+----------+" + "| | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + column_span_invalid_position_row, + Matrix::new(3, 3).with(Modify::new((1000, 0)).with(Span::column(2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + column_span_invalid_position_column, + Matrix::new(3, 3).with(Modify::new((0, 1000)).with(Span::column(2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + column_span_invalid_position_row_and_column, + Matrix::new(3, 3).with(Modify::new((1000, 1000)).with(Span::column(2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + row_span_invalid_position_row, + Matrix::new(3, 3).with(Modify::new((1000, 0)).with(Span::row(2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + row_span_invalid_position_column, + Matrix::new(3, 3).with(Modify::new((0, 1000)).with(Span::row(2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + row_span_invalid_position_row_and_column, + Matrix::new(3, 3).with(Modify::new((1000, 1000)).with(Span::row(2))), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + fix_qc_0, + { + let data: [[i64; 39]; 2] = [[2542785870, 2382388818, 2879895075, 2885436543, 2331131758, 219892320, 2503640226, 3754929678, 2206481860, 686909682, 3456499235, 931699300, 1556722454, 958179233, 3896072307, 2042612749, 3354379549, 3272539286, 3926297167, 4294967295, 1650407458, 3322068437, 4294967295, 446762625, 829020202, 4150192304, 3430619243, 3460609391, 2992017103, 513091574, 1514148367, 2166549688, 1401371431, 2854075038, 1286733939, 2959901405, 4152658371, 0, 4224074215], [360331598, 3736108702, 2948800064, 2121584548, 1609988995, 469935087, 3974876615, 2193609088, 3568111892, 732365859, 0, 4294967295, 2994498036, 198522721, 1784359340, 1, 2732726754, 592359359, 3016729802, 878533877, 2997437699, 3573361662, 1111570515, 4294967295, 2245782848, 1383106893, 0, 0, 2869976103, 1611436878, 1682224972, 3249055253, 1562255501, 1370527728, 240481955, 334260406, 2247343342, 3000635978, 395723768]]; + let row_spans = [2, 1, 27, 111, 226, 221, 121, 22, 252, 30, 115, 85, 255, 126, 26, 245, 36, 50, 255, 211, 47, 114, 174, 173, 145, 138, 78, 198, 253, 229, 151, 243, 242, 30, 52, 116, 177, 25, 1, 32, 28, 48, 225, 103, 17, 243, 0, 128, 69, 206, 221, 105, 239, 74, 184, 48, 178, 237, 120, 228, 184, 1, 132, 118, 14, 187]; + let col_spans = [7, 91, 56, 246, 73]; + + let data = data.iter().map(|row| row.iter().map(ToString::to_string)); + let rspans = create_span_list(2, 39).zip(row_spans.iter()).map(|(pos, span)| Modify::new(pos).with(Span::column(*span))).collect::<Vec<_>>(); + let cspans = create_span_list(2, 39).zip(col_spans.iter()).map(|(pos, span)| Modify::new(pos).with(Span::row(*span))).collect::<Vec<_>>(); + + Builder::from_iter(data).build().with(Style::ascii()).with(rspans).with(cspans).to_string() + }, + "+------+-----++++++++++++++++++++++++++++------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+" + "| 2542785870 | 2879895075 | 513091574 |" + "+ + +------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+" + "| | | 1611436878 | 1682224972 | 3249055253 | 1562255501 | 1370527728 | 240481955 | 334260406 | 2247343342 | 3000635978 | 395723768 |" + "+------+-----++++++++++++++++++++++++++++------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+" +); + +fn create_span_list(count_rows: usize, count_cols: usize) -> impl Iterator<Item = Position> { + (0..count_rows).flat_map(move |r| (0..count_cols).map(move |c| (r, c))) +} diff --git a/vendor/tabled/tests/settings/split_test.rs b/vendor/tabled/tests/settings/split_test.rs new file mode 100644 index 000000000..ac13d8071 --- /dev/null +++ b/vendor/tabled/tests/settings/split_test.rs @@ -0,0 +1,277 @@ +#![cfg(feature = "std")] + +use std::iter::FromIterator; + +use tabled::{builder::Builder, settings::split::Split, Table}; + +use testing_table::test_table; + +test_table!( + split_column_test, + Table::from_iter(['a'..='z']).with(Split::column(12)), + "+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | g | h | i | j | k | l |" + "+---+---+---+---+---+---+---+---+---+---+---+---+" + "| m | n | o | p | q | r | s | t | u | v | w | x |" + "+---+---+---+---+---+---+---+---+---+---+---+---+" + "| y | z | | | | | | | | | | |" + "+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_column_2_test, + Table::from_iter(['a'..='z']) + .with(Split::column(12)) + .with(Split::column(4)), + "+---+---+---+---+" + "| a | b | c | d |" + "+---+---+---+---+" + "| e | f | g | h |" + "+---+---+---+---+" + "| i | j | k | l |" + "+---+---+---+---+" + "| m | n | o | p |" + "+---+---+---+---+" + "| q | r | s | t |" + "+---+---+---+---+" + "| u | v | w | x |" + "+---+---+---+---+" + "| y | z | | |" + "+---+---+---+---+" +); + +test_table!( + split_column_retain_test, + Table::from_iter(['a'..='z']) + .with(Split::column(12)) + .with(Split::column(4).retain()), + "+---+---+---+---+" + "| a | b | c | d |" + "+---+---+---+---+" + "| e | f | g | h |" + "+---+---+---+---+" + "| i | j | k | l |" + "+---+---+---+---+" + "| m | n | o | p |" + "+---+---+---+---+" + "| q | r | s | t |" + "+---+---+---+---+" + "| u | v | w | x |" + "+---+---+---+---+" + "| y | z | | |" + "+---+---+---+---+" + "| | | | |" + "+---+---+---+---+" + "| | | | |" + "+---+---+---+---+" +); + +test_table!( + split_row_test, + Table::from_iter(['a'..='z']) + .with(Split::column(12)) + .with(Split::column(4)) + .with(Split::row(1).concat()), // take it back to the original shape + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_row_2_test, + Table::from_iter(['a'..='z']) + .with(Split::column(12)) + .with(Split::column(4)) + .with(Split::row(2).concat()), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | i | j | k | l | q | r | s | t | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| e | f | g | h | m | n | o | p | u | v | w | x | | |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_colum_index_beyond_size_test, + Table::from_iter(['a'..='z']) + .with(Split::column(10000)), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_row_index_beyond_size_test, + Table::from_iter(['a'..='z']) + .with(Split::row(10000)), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_empty_table_test, + Builder::default().build().with(Split::column(10000)), + "" +); + +test_table!( + split_column_zero_argument_test, + Table::from_iter(['a'..='z']).with(Split::column(0)), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_row_zero_argument_test, + Table::from_iter(['a'..='z']).with(Split::row(0)), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_blank_table_test, + Table::from_iter([vec![String::new(); 26]]).with(Split::column(12)), + "+--+--+--+--+--+--+--+--+--+--+--+--+" + "| | | | | | | | | | | | |" // first section is protected + "+--+--+--+--+--+--+--+--+--+--+--+--+" +); + +test_table!( + split_blank_table_2_test, + Table::from_iter([vec![String::new(); 26]]).with(Split::column(12).retain()), + "+--+--+--+--+--+--+--+--+--+--+--+--+" + "| | | | | | | | | | | | |" + "+--+--+--+--+--+--+--+--+--+--+--+--+" + "| | | | | | | | | | | | |" + "+--+--+--+--+--+--+--+--+--+--+--+--+" + "| | | | | | | | | | | | |" + "+--+--+--+--+--+--+--+--+--+--+--+--+" +); + +test_table!( + split_zip_test, + Table::from_iter(['a'..='z']) + .with(Split::column(6)) + .with(Split::row(2)), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | m | y | b | n | z | c | o | d | p | e | q | f | r |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| g | s | | h | t | | i | u | j | v | k | w | l | x |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_concat_test, + Table::from_iter(['a'..='z']) + .with(Split::column(6)) + .with(Split::row(2).concat()), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | b | c | d | e | f | m | n | o | p | q | r | y | z |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| g | h | i | j | k | l | s | t | u | v | w | x | | |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_clean_test, + Table::from_iter(['a'..='z']) + .with(Split::column(12)) + .with(Split::row(2)), + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| a | y | b | z | c | d | e | f | g | h | i | j | k | l |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" + "| m | | n | | o | p | q | r | s | t | u | v | w | x |" + "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" +); + +test_table!( + split_retain_test, + Table::from_iter(['a'..='z']) + .with(Split::column(12)) + .with(Split::row(2).retain()), + "+---+---+---+---+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+" + "| a | y | b | z | c | | d | | e | | f | | g | | h | | i | | j | | k | | l | |" + "+---+---+---+---+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+" + "| m | | n | | o | | p | | q | | r | | s | | t | | u | | v | | w | | x | |" + "+---+---+---+---+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+" +); + +test_table!( + split_mostly_blank_test, + Table::from_iter([vec![ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "A", + ]]).with(Split::column(5)) + .with(Split::row(2)), + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | A | |" + "+--+--+--+---+--+" +); + +test_table!( + split_mostly_blank_retain_test, + Table::from_iter([vec![ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "A", + ]]).with(Split::column(5).retain()), + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | | |" + "+--+--+--+---+--+" + "| | | | A | |" + "+--+--+--+---+--+" +); + +test_table!( + split_scattered_values_test, + Table::from_iter([vec![ + "", "", "", "", "", "g", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "A", + ]]).with(Split::column(5)), + "+---+--+--+---+--+" + "| | | | | |" + "+---+--+--+---+--+" + "| g | | | | |" + "+---+--+--+---+--+" + "| | | | A | |" + "+---+--+--+---+--+" +); + +test_table!( + split_scattered_values_column_and_row_test, + Table::from_iter([vec![ + "", "", "", "", "", "g", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "A", + ]]).with(Split::column(5)).with(Split::row(2)), + "+---+--+--+--+---+--+" + "| | | | | A | |" + "+---+--+--+--+---+--+" + "| g | | | | | |" + "+---+--+--+--+---+--+" +); + +test_table!( + split_scattered_values_column_and_row_retain_test, + Table::from_iter([vec![ + "", "", "", "", "", "g", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "A", + ]]).with(Split::column(5).retain()).with(Split::row(2).retain()), + "+---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+--+--+--+--+" + "| | | | | | | | | | | | | | | | A | | | | |" + "+---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+--+--+--+--+" + "| g | | | | | | | | | | | | | | | | | | | |" + "+---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+--+--+--+--+" +); diff --git a/vendor/tabled/tests/settings/style_test.rs b/vendor/tabled/tests/settings/style_test.rs new file mode 100644 index 000000000..884192471 --- /dev/null +++ b/vendor/tabled/tests/settings/style_test.rs @@ -0,0 +1,2606 @@ +#![cfg(feature = "std")] + +use std::iter::FromIterator; + +use tabled::{ + builder::Builder, + settings::{ + object::{Columns, Rows, Segment}, + style::{ + Border, BorderChar, BorderColor, BorderSpanCorrection, BorderText, HorizontalLine, + Line, Offset, RawStyle, Style, VerticalLine, + }, + Color, Format, Highlight, Modify, Padding, Span, + }, + Table, +}; + +use crate::matrix::Matrix; +use testing_table::{static_table, test_table}; + +#[cfg(feature = "color")] +use ::{owo_colors::OwoColorize, std::convert::TryFrom}; + +test_table!( + default_style, + Matrix::new(3, 3).with(Style::ascii()), + "+---+----------+----------+----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+----------+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+---+----------+----------+----------+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+---+----------+----------+----------+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+---+----------+----------+----------+" +); + +test_table!( + psql_style, + Matrix::new(3, 3).with(Style::psql()), + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | 0-0 | 0-1 | 0-2 " + " 1 | 1-0 | 1-1 | 1-2 " + " 2 | 2-0 | 2-1 | 2-2 " +); + +test_table!( + markdown_style, + Matrix::new(3, 3).with(Style::markdown()), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + modern_style, + Matrix::new(3, 3).with(Style::modern()), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + rounded_style, + Matrix::new(3, 3).with(Style::rounded()), + "╭───┬──────────┬──────────┬──────────╮" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "╰───┴──────────┴──────────┴──────────╯" +); + +test_table!( + sharp_style, + Matrix::new(3, 3).with(Style::sharp()), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + modern_clean_style, + Matrix::new(3, 3).with(Style::modern().remove_horizontal().horizontals(vec![HorizontalLine::new(1, Style::modern().get_horizontal())])), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + blank_style, + Matrix::new(3, 3).with(Style::blank()), + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " +); + +test_table!( + extended_style, + Matrix::new(3, 3).with(Style::extended()), + "╔═══╦══════════╦══════════╦══════════╗" + "║ N ║ column 0 ║ column 1 ║ column 2 ║" + "╠═══╬══════════╬══════════╬══════════╣" + "║ 0 ║ 0-0 ║ 0-1 ║ 0-2 ║" + "╠═══╬══════════╬══════════╬══════════╣" + "║ 1 ║ 1-0 ║ 1-1 ║ 1-2 ║" + "╠═══╬══════════╬══════════╬══════════╣" + "║ 2 ║ 2-0 ║ 2-1 ║ 2-2 ║" + "╚═══╩══════════╩══════════╩══════════╝" +); + +test_table!( + ascii_dots_style, + Matrix::new(3, 3).with(Style::dots()), + "......................................" + ": N : column 0 : column 1 : column 2 :" + ":...:..........:..........:..........:" + ": 0 : 0-0 : 0-1 : 0-2 :" + ":...:..........:..........:..........:" + ": 1 : 1-0 : 1-1 : 1-2 :" + ":...:..........:..........:..........:" + ": 2 : 2-0 : 2-1 : 2-2 :" + ":...:..........:..........:..........:" +); + +test_table!( + re_structured_text_style, + Matrix::new(3, 3).with(Style::re_structured_text()), + "=== ========== ========== ==========" + " N column 0 column 1 column 2 " + "=== ========== ========== ==========" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + "=== ========== ========== ==========" +); + +test_table!( + ascii_rounded_style, + Matrix::new(3, 3).with(Style::ascii_rounded()), + ".------------------------------------." + "| N | column 0 | column 1 | column 2 |" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + "'------------------------------------'" +); + +test_table!( + style_head_changes, + Matrix::new(3, 3).with(Style::modern().remove_horizontal()), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + style_frame_changes, + Matrix::new(3, 3).with(Style::modern().remove_top().remove_bottom().remove_horizontal()), + "│ N │ column 0 │ column 1 │ column 2 │" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" +); + +test_table!( + custom_style, + Matrix::new(3, 3) + .with(Style::blank() + .bottom('*') + .vertical('\'') + .horizontal('`') + .intersection('\'') + .intersection_bottom('\'') + .horizontals(vec![HorizontalLine::new(1, Line::full('x', '*', 'q', 'w'))])), + " N ' column 0 ' column 1 ' column 2 " + "qxxx*xxxxxxxxxx*xxxxxxxxxx*xxxxxxxxxxw" + " 0 ' 0-0 ' 0-1 ' 0-2 " + " ```'``````````'``````````'`````````` " + " 1 ' 1-0 ' 1-1 ' 1-2 " + " ```'``````````'``````````'`````````` " + " 2 ' 2-0 ' 2-1 ' 2-2 " + " ***'**********'**********'********** " +); + +test_table!( + style_single_cell_0, + Matrix::table(0, 0), + "+---+" + "| N |" + "+---+" +); + +test_table!( + style_single_cell_1, + Matrix::table(0, 0).with(Style::blank()), + " N " +); + +test_table!( + top_border_override_first_test, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(Rows::first())), + "-Table---------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + top_border_override_last_test, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(Rows::last())), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "-Table---------+----------+" +); + +test_table!( + top_border_override_new_test, + Matrix::table(2, 2) + .with(BorderText::new("-Table").horizontal(1)) + .with(BorderText::new("-Table").horizontal(2)), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "-Table---------+----------+" + "| 0 | 0-0 | 0-1 |" + "-Table---------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + top_border_override_new_doesnt_panic_when_index_is_invalid, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(100)), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + top_override_doesnt_work_with_style_with_no_top_border_test, + Matrix::table(2, 2) + .with(Style::psql()) + .with(BorderText::new("-Table").horizontal(Rows::first())), + " N | column 0 | column 1 " + "---+----------+----------" + " 0 | 0-0 | 0-1 " + " 1 | 1-0 | 1-1 " +); + +test_table!( + top_border_override_cleared_after_restyling_test, + Matrix::table(2, 2) + .with(BorderText::new("-Table").horizontal(Rows::first())) + .with(Style::ascii()), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + top_border_override_with_big_string_test, + Matrix::table(2, 2) + .with(BorderText::new("-Tableeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1231").horizontal(Rows::first())), + "-Tableeeeeeeeeeeeeeeeeeeeee" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_0, + Matrix::table(2, 2) + .with(Style::empty()) + .with(Modify::new(Rows::first()).with(Border::default().bottom('-'))) + .with(BorderText::new("-Table").horizontal(1)), + " N column 0 column 1 " + "-Table-----------------" + " 0 0-0 0-1 " + " 1 1-0 1-1 " +); + +test_table!( + border_color_global, + { Matrix::table(2, 2).with(BorderColor::default().bottom(Color::FG_RED)) }, + "+---+----------+----------+\n\ + | N | column 0 | column 1 |\n\ + +\u{1b}[31m---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\n\ + | 0 | 0-0 | 0-1 |\n\ + +\u{1b}[31m---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\n\ + | 1 | 1-0 | 1-1 |\n\ + +\u{1b}[31m---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+" +); + +#[cfg(feature = "color")] +test_table!( + border_text_colored, + Matrix::table(2, 2) + .with(BorderText::new("-Table").horizontal(1)) + .with(BorderText::new("-Table213123").horizontal(2)) + .with(Modify::new(Rows::single(1)).with(BorderColor::default().bottom(Color::FG_RED))) + .with(Modify::new(Rows::single(2)).with(BorderColor::default().bottom(Color::try_from(" ".blue().on_green().to_string()).unwrap()))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "-Table---------+----------+" + "| 0 | 0-0 | 0-1 |" + "-\u{1b}[31mTab\u{1b}[39ml\u{1b}[31me213123---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+" + "| 1 | 1-0 | 1-1 |" + "+\u{1b}[34m\u{1b}[42m---\u{1b}[39m\u{1b}[49m+\u{1b}[34m\u{1b}[42m----------\u{1b}[39m\u{1b}[49m+\u{1b}[34m\u{1b}[42m----------\u{1b}[39m\u{1b}[49m+" +); + +test_table!( + border_text_offset_test_0, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(1).offset(Offset::Begin(5))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+-Table----+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_offset_test_1, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(1).offset(Offset::Begin(15))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+-----------Table-----+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_offset_test_2, + Matrix::table(2, 2).with(BorderText::new("Table").horizontal(1).offset(Offset::End(5))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+------Table" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_offset_test_3, + Matrix::table(2, 2).with(BorderText::new("Table").horizontal(1).offset(Offset::End(15))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+-------Table---------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_offset_test_4, + Matrix::table(2, 2).with(BorderText::new("Table").horizontal(1).offset(Offset::End(21))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+-Table----+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_offset_test_5, + Matrix::table(2, 2).with(BorderText::new("Table").horizontal(1).offset(Offset::End(25))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+-Table--------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_text_offset_test_6, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(1).offset(Offset::Begin(21))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+------Table" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_override_color, + Matrix::table(2, 2).with(BorderText::new("-Table").horizontal(Rows::first()).color(Color::FG_BLUE)), + "\u{1b}[34m-\u{1b}[39m\u{1b}[34mT\u{1b}[39m\u{1b}[34ma\u{1b}[39m\u{1b}[34mb\u{1b}[39m\u{1b}[34ml\u{1b}[39m\u{1b}[34me\u{1b}[39m---------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + empty_style, + Matrix::new(3, 3) + .with(Style::empty()) + .with(Modify::new(Segment::all()).with(Padding::zero())), + "Ncolumn 0column 1column 2" + "0 0-0 0-1 0-2 " + "1 1-0 1-1 1-2 " + "2 2-0 2-1 2-2 " +); + +test_table!( + single_column_style_0, + Matrix::table(2, 0).with(Style::modern()), + "┌───┐" + "│ N │" + "├───┤" + "│ 0 │" + "├───┤" + "│ 1 │" + "└───┘" +); + +test_table!( + single_column_style_1, + Matrix::table(2, 0).with(Style::blank()), + " N " + " 0 " + " 1 " +); + +test_table!( + single_column_last_row_style, + Matrix::table(3, 0).with(Style::re_structured_text()), + "===" + " N " + "===" + " 0 " + " 1 " + " 2 " + "===" +); + +test_table!( + single_cell_style, + Builder::from_iter([[""]]).build().with(Style::modern()), + "┌──┐" + "│ │" + "└──┘" +); + +test_table!( + border_test_0, + Matrix::table(2, 2).with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "*###*##########*##########*" + "* 0 * 0-0 * 0-1 *" + "***************************" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_test_1, + Matrix::table(2, 2) + .with(Style::empty()) + .with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))), + " N column 0 column 1 " + "*###*##########*##########*" + "* 0 * 0-0 * 0-1 *" + "***************************" + " 1 1-0 1-1 " +); + +test_table!( + style_frame_test_0, + Matrix::table(2, 2).with(Highlight::new(Rows::single(1), Style::modern().get_frame())), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "┌─────────────────────────┐" + "│ 0 | 0-0 | 0-1 │" + "└─────────────────────────┘" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + style_frame_test_1, + Matrix::table(2, 2) + .with(Style::blank()) + .with(Highlight::new(Rows::single(0), Style::extended().get_frame())) + .with(Highlight::new(Rows::single(2), Style::extended().get_frame())), + "╔═════════════════════════╗" + "║ N column 0 column 1 ║" + "╚═════════════════════════╝" + " 0 0-0 0-1 " + "╔═════════════════════════╗" + "║ 1 1-0 1-1 ║" + "╚═════════════════════════╝" +); + +test_table!( + single_column_off_horizontal_test, + Matrix::table(3, 0).with(Style::ascii().remove_horizontal().remove_vertical()), + "+---+" + "| N |" + "| 0 |" + "| 1 |" + "| 2 |" + "+---+" +); + +test_table!( + single_row_test, + Matrix::table(0, 3).with(Style::modern()), + "┌───┬──────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "└───┴──────────┴──────────┴──────────┘" +); + +test_table!( + empty_border_text_doesnt_panic_test, + Matrix::table(2, 2).with(BorderText::new("").horizontal(0)), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + span_correct_test_0, + Matrix::table(6, 4) + .with(Modify::new((0, 3)).with(Span::column(2))) + .with(Modify::new((1, 0)).with(Span::column(3))) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(Modify::new((2, 3)).with(Span::column(2))) + .with(Modify::new((3, 0)).with(Span::column(5))) + .with(Modify::new((4, 1)).with(Span::column(4))) + .with(Modify::new((5, 0)).with(Span::column(5))) + .with(Modify::new((6, 0)).with(Span::column(5))) + .with(BorderSpanCorrection), + "+---+----------+----------+-----------+" + "| N | column 0 | column 1 | column 2 |" + "+---+----------+----------+-----+-----+" + "| 0 | 0-2 | 0-3 |" + "+--------------+----------+-----+-----+" + "| 1 | 1-1 | 1-2 |" + "+--------------+----------+-----------+" + "| 2 |" + "+---+---------------------------------+" + "| 3 | 3-0 |" + "+---+---------------------------------+" + "| 4 |" + "+-------------------------------------+" + "| 5 |" + "+-------------------------------------+" +); + +test_table!( + span_correct_test_1, + Matrix::table(6, 4) + .with(Modify::new((0, 0)).with(Span::column(5))) + .with(Modify::new((1, 0)).with(Span::column(3))) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(Modify::new((2, 3)).with(Span::column(2))) + .with(Modify::new((3, 0)).with(Span::column(5))) + .with(Modify::new((4, 1)).with(Span::column(4))) + .with(Modify::new((5, 0)).with(Span::column(5))) + .with(Modify::new((6, 0)).with(Span::column(5))) + .with(BorderSpanCorrection), + "+----------------------+" + "| N |" + "+----------+-----+-----+" + "| 0 | 0-2 | 0-3 |" + "+----+-----+-----+-----+" + "| 1 | 1-1 | 1-2 |" + "+----+-----+-----------+" + "| 2 |" + "+---+------------------+" + "| 3 | 3-0 |" + "+---+------------------+" + "| 4 |" + "+----------------------+" + "| 5 |" + "+----------------------+" +); + +test_table!( + style_settings_usage_test_0, + Matrix::new(3, 3) + .insert((1, 1), "a longer string") + .with({ + let mut style: RawStyle = Style::modern().into(); + style + .set_bottom(Some('a')) + .set_left(Some('b')) + .set_right(None) + .set_top(None) + .set_intersection(Some('x')) + .set_intersection_top(None) + .set_corner_top_left(None) + .set_corner_top_right(None); + style + }), + "b N │ column 0 │ column 1 │ column 2 " + "├───x─────────────────x──────────x──────────┤" + "b 0 │ a longer string │ 0-1 │ 0-2 " + "├───x─────────────────x──────────x──────────┤" + "b 1 │ 1-0 │ 1-1 │ 1-2 " + "├───x─────────────────x──────────x──────────┤" + "b 2 │ 2-0 │ 2-1 │ 2-2 " + "└aaa┴aaaaaaaaaaaaaaaaa┴aaaaaaaaaa┴aaaaaaaaaa┘" +); + +test_table!( + style_settings_usage_test_1, + Matrix::new(3, 3) + .insert((1, 1), "a longer string") + .with({ + let mut style: RawStyle = Style::modern().into(); + style.set_bottom(None); + style + }), + "┌───┬─────────────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼─────────────────┼──────────┼──────────┤" + "│ 0 │ a longer string │ 0-1 │ 0-2 │" + "├───┼─────────────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "├───┼─────────────────┼──────────┼──────────┤" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "└ ┴ ┴ ┴ ┘" +); + +test_table!( + style_settings_usage_test_2, + Matrix::new(3, 3) + .insert((1, 1), "a longer string") + .with({ + let mut style: RawStyle = Style::modern().into(); + style.set_bottom(None); + style + }) + .with(Modify::new(Rows::last()).with(Border::default().corner_bottom_left('*'))), + "┌───┬─────────────────┬──────────┬──────────┐" + "│ N │ column 0 │ column 1 │ column 2 │" + "├───┼─────────────────┼──────────┼──────────┤" + "│ 0 │ a longer string │ 0-1 │ 0-2 │" + "├───┼─────────────────┼──────────┼──────────┤" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "├───┼─────────────────┼──────────┼──────────┤" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "* * * * ┘" +); + +test_table!( + border_none_test_0, + Matrix::table(2, 2) + .with(Style::ascii()) + .with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))) + .with(Modify::new(Rows::single(1)).with(Border::empty())), + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "+---+----------+----------+" + "| 0 | 0-0 | 0-1 |" + "+---+----------+----------+" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" +); + +test_table!( + border_none_test_1, + Matrix::table(2, 2) + .with(Style::empty()) + .with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))) + .with(Modify::new(Columns::single(1)).with(Border::empty())), + " N column 0 column 1 " + "*### ##########*" + "* 0 0-0 0-1 *" + "**** ***********" + " 1 1-0 1-1 " +); + +#[test] +fn custom_style_test() { + macro_rules! test_style { + ($style:expr, $expected:expr $(,)*) => { + let table = Matrix::new(3, 3).with($style).to_string(); + assert_eq!(table, $expected); + }; + } + + // Single + + test_style!( + Style::empty().top('-'), + static_table!( + "---------------------------------" + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ), + ); + test_style!( + Style::empty().bottom('-'), + static_table!( + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + "---------------------------------" + ), + ); + test_style!( + Style::empty().left('-'), + static_table!( + "- N column 0 column 1 column 2 " + "- 0 0-0 0-1 0-2 " + "- 1 1-0 1-1 1-2 " + "- 2 2-0 2-1 2-2 " + ), + ); + test_style!( + Style::empty().right('-'), + static_table!( + " N column 0 column 1 column 2 -" + " 0 0-0 0-1 0-2 -" + " 1 1-0 1-1 1-2 -" + " 2 2-0 2-1 2-2 -" + ), + ); + test_style!( + Style::empty().horizontal('-'), + static_table!( + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + "---------------------------------" + " 1 1-0 1-1 1-2 " + "---------------------------------" + " 2 2-0 2-1 2-2 " + ), + ); + test_style!( + Style::empty().horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]), + static_table!( + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ), + ); + test_style!( + Style::empty().vertical('-'), + static_table!( + " N - column 0 - column 1 - column 2 " + " 0 - 0-0 - 0-1 - 0-2 " + " 1 - 1-0 - 1-1 - 1-2 " + " 2 - 2-0 - 2-1 - 2-2 " + ), + ); + + // Combinations + + test_style!( + Style::empty().top('-').bottom('+'), + static_table!( + "---------------------------------" + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + "+++++++++++++++++++++++++++++++++" + ) + ); + test_style!( + Style::empty().top('-').left('+'), + static_table!( + "+---------------------------------" + "+ N column 0 column 1 column 2 " + "+ 0 0-0 0-1 0-2 " + "+ 1 1-0 1-1 1-2 " + "+ 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty().top('-').right('+'), + static_table!( + "---------------------------------+" + " N column 0 column 1 column 2 +" + " 0 0-0 0-1 0-2 +" + " 1 1-0 1-1 1-2 +" + " 2 2-0 2-1 2-2 +" + ) + ); + test_style!( + Style::empty().top('-').horizontal('+'), + static_table!( + "---------------------------------" + " N column 0 column 1 column 2 " + "+++++++++++++++++++++++++++++++++" + " 0 0-0 0-1 0-2 " + "+++++++++++++++++++++++++++++++++" + " 1 1-0 1-1 1-2 " + "+++++++++++++++++++++++++++++++++" + " 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty().top('-').vertical('+'), + static_table!( + "---+----------+----------+----------" + " N + column 0 + column 1 + column 2 " + " 0 + 0-0 + 0-1 + 0-2 " + " 1 + 1-0 + 1-1 + 1-2 " + " 2 + 2-0 + 2-1 + 2-2 " + ) + ); + test_style!( + Style::empty() + .top('-') + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), + static_table!( + "---------------------------------" + " N column 0 column 1 column 2 " + "+++++++++++++++++++++++++++++++++" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + ); + + test_style!( + Style::empty().bottom('-').top('+'), + static_table!( + "+++++++++++++++++++++++++++++++++" + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + "---------------------------------" + ) + ); + test_style!( + Style::empty().bottom('-').left('+'), + static_table!( + "+ N column 0 column 1 column 2 " + "+ 0 0-0 0-1 0-2 " + "+ 1 1-0 1-1 1-2 " + "+ 2 2-0 2-1 2-2 " + "+---------------------------------" + ) + ); + test_style!( + Style::empty().bottom('-').right('+'), + static_table!( + " N column 0 column 1 column 2 +" + " 0 0-0 0-1 0-2 +" + " 1 1-0 1-1 1-2 +" + " 2 2-0 2-1 2-2 +" + "---------------------------------+" + ) + ); + test_style!( + Style::empty().bottom('-').vertical('+'), + static_table!( + " N + column 0 + column 1 + column 2 " + " 0 + 0-0 + 0-1 + 0-2 " + " 1 + 1-0 + 1-1 + 1-2 " + " 2 + 2-0 + 2-1 + 2-2 " + "---+----------+----------+----------" + ) + ); + test_style!( + Style::empty().bottom('-').horizontal('+'), + static_table!( + " N column 0 column 1 column 2 " + "+++++++++++++++++++++++++++++++++" + " 0 0-0 0-1 0-2 " + "+++++++++++++++++++++++++++++++++" + " 1 1-0 1-1 1-2 " + "+++++++++++++++++++++++++++++++++" + " 2 2-0 2-1 2-2 " + "---------------------------------" + ) + ); + test_style!( + Style::empty() + .bottom('-') + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), + static_table!( + " N column 0 column 1 column 2 " + "+++++++++++++++++++++++++++++++++" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + "---------------------------------" + ) + ); + + test_style!( + Style::empty().left('-').top('+'), + static_table!( + "++++++++++++++++++++++++++++++++++" + "- N column 0 column 1 column 2 " + "- 0 0-0 0-1 0-2 " + "- 1 1-0 1-1 1-2 " + "- 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty().left('-').bottom('+'), + static_table!( + "- N column 0 column 1 column 2 " + "- 0 0-0 0-1 0-2 " + "- 1 1-0 1-1 1-2 " + "- 2 2-0 2-1 2-2 " + "++++++++++++++++++++++++++++++++++" + ) + ); + test_style!( + Style::empty().left('-').right('+'), + static_table!( + "- N column 0 column 1 column 2 +" + "- 0 0-0 0-1 0-2 +" + "- 1 1-0 1-1 1-2 +" + "- 2 2-0 2-1 2-2 +" + ) + ); + test_style!( + Style::empty().left('-').vertical('+'), + static_table!( + "- N + column 0 + column 1 + column 2 " + "- 0 + 0-0 + 0-1 + 0-2 " + "- 1 + 1-0 + 1-1 + 1-2 " + "- 2 + 2-0 + 2-1 + 2-2 " + ) + ); + test_style!( + Style::empty().left('-').horizontal('+'), + static_table!( + "- N column 0 column 1 column 2 " + "++++++++++++++++++++++++++++++++++" + "- 0 0-0 0-1 0-2 " + "++++++++++++++++++++++++++++++++++" + "- 1 1-0 1-1 1-2 " + "++++++++++++++++++++++++++++++++++" + "- 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty() + .left('-') + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), + static_table!( + "- N column 0 column 1 column 2 " + " +++++++++++++++++++++++++++++++++" + "- 0 0-0 0-1 0-2 " + "- 1 1-0 1-1 1-2 " + "- 2 2-0 2-1 2-2 " + ) + ); + + test_style!( + Style::empty().right('-').top('+'), + static_table!( + "++++++++++++++++++++++++++++++++++" + " N column 0 column 1 column 2 -" + " 0 0-0 0-1 0-2 -" + " 1 1-0 1-1 1-2 -" + " 2 2-0 2-1 2-2 -" + ) + ); + test_style!( + Style::empty().right('-').bottom('+'), + static_table!( + " N column 0 column 1 column 2 -" + " 0 0-0 0-1 0-2 -" + " 1 1-0 1-1 1-2 -" + " 2 2-0 2-1 2-2 -" + "++++++++++++++++++++++++++++++++++" + ) + ); + test_style!( + Style::empty().right('-').left('+'), + static_table!( + "+ N column 0 column 1 column 2 -" + "+ 0 0-0 0-1 0-2 -" + "+ 1 1-0 1-1 1-2 -" + "+ 2 2-0 2-1 2-2 -" + ) + ); + test_style!( + Style::empty().right('-').vertical('+'), + static_table!( + " N + column 0 + column 1 + column 2 -" + " 0 + 0-0 + 0-1 + 0-2 -" + " 1 + 1-0 + 1-1 + 1-2 -" + " 2 + 2-0 + 2-1 + 2-2 -" + ) + ); + test_style!( + Style::empty().right('-').horizontal('+'), + static_table!( + " N column 0 column 1 column 2 -" + "++++++++++++++++++++++++++++++++++" + " 0 0-0 0-1 0-2 -" + "++++++++++++++++++++++++++++++++++" + " 1 1-0 1-1 1-2 -" + "++++++++++++++++++++++++++++++++++" + " 2 2-0 2-1 2-2 -" + ) + ); + test_style!( + Style::empty() + .right('-') + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), + static_table!( + " N column 0 column 1 column 2 -" + "+++++++++++++++++++++++++++++++++ " + " 0 0-0 0-1 0-2 -" + " 1 1-0 1-1 1-2 -" + " 2 2-0 2-1 2-2 -" + ) + ); + + test_style!( + Style::empty().vertical('-').top('+'), + static_table!( + "++++++++++++++++++++++++++++++++++++" + " N - column 0 - column 1 - column 2 " + " 0 - 0-0 - 0-1 - 0-2 " + " 1 - 1-0 - 1-1 - 1-2 " + " 2 - 2-0 - 2-1 - 2-2 " + ) + ); + test_style!( + Style::empty().vertical('-').bottom('+'), + static_table!( + " N - column 0 - column 1 - column 2 " + " 0 - 0-0 - 0-1 - 0-2 " + " 1 - 1-0 - 1-1 - 1-2 " + " 2 - 2-0 - 2-1 - 2-2 " + "++++++++++++++++++++++++++++++++++++" + ) + ); + test_style!( + Style::empty().vertical('-').left('+'), + static_table!( + "+ N - column 0 - column 1 - column 2 " + "+ 0 - 0-0 - 0-1 - 0-2 " + "+ 1 - 1-0 - 1-1 - 1-2 " + "+ 2 - 2-0 - 2-1 - 2-2 " + ) + ); + test_style!( + Style::empty().vertical('-').right('+'), + static_table!( + " N - column 0 - column 1 - column 2 +" + " 0 - 0-0 - 0-1 - 0-2 +" + " 1 - 1-0 - 1-1 - 1-2 +" + " 2 - 2-0 - 2-1 - 2-2 +" + ) + ); + test_style!( + Style::empty().vertical('-').horizontal('+'), + static_table!( + " N - column 0 - column 1 - column 2 " + "++++++++++++++++++++++++++++++++++++" + " 0 - 0-0 - 0-1 - 0-2 " + "++++++++++++++++++++++++++++++++++++" + " 1 - 1-0 - 1-1 - 1-2 " + "++++++++++++++++++++++++++++++++++++" + " 2 - 2-0 - 2-1 - 2-2 " + ) + ); + test_style!( + Style::empty() + .vertical('-') + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), + static_table!( + " N - column 0 - column 1 - column 2 " + "+++ ++++++++++ ++++++++++ ++++++++++" + " 0 - 0-0 - 0-1 - 0-2 " + " 1 - 1-0 - 1-1 - 1-2 " + " 2 - 2-0 - 2-1 - 2-2 " + ) + ); + + test_style!( + Style::empty().horizontal('-').top('+'), + static_table!( + "+++++++++++++++++++++++++++++++++" + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + "---------------------------------" + " 1 1-0 1-1 1-2 " + "---------------------------------" + " 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty().horizontal('-').bottom('+'), + static_table!( + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + "---------------------------------" + " 1 1-0 1-1 1-2 " + "---------------------------------" + " 2 2-0 2-1 2-2 " + "+++++++++++++++++++++++++++++++++" + ) + ); + test_style!( + Style::empty().horizontal('-').left('+'), + static_table!( + "+ N column 0 column 1 column 2 " + "+---------------------------------" + "+ 0 0-0 0-1 0-2 " + "+---------------------------------" + "+ 1 1-0 1-1 1-2 " + "+---------------------------------" + "+ 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty().horizontal('-').right('+'), + static_table!( + " N column 0 column 1 column 2 +" + "---------------------------------+" + " 0 0-0 0-1 0-2 +" + "---------------------------------+" + " 1 1-0 1-1 1-2 +" + "---------------------------------+" + " 2 2-0 2-1 2-2 +" + ) + ); + test_style!( + Style::empty().horizontal('-').vertical('+'), + static_table!( + " N + column 0 + column 1 + column 2 " + "---+----------+----------+----------" + " 0 + 0-0 + 0-1 + 0-2 " + "---+----------+----------+----------" + " 1 + 1-0 + 1-1 + 1-2 " + "---+----------+----------+----------" + " 2 + 2-0 + 2-1 + 2-2 " + ) + ); + test_style!( + Style::empty() + .horizontal('-') + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), + static_table!( + " N column 0 column 1 column 2 " + "+++++++++++++++++++++++++++++++++" + " 0 0-0 0-1 0-2 " + "---------------------------------" + " 1 1-0 1-1 1-2 " + "---------------------------------" + " 2 2-0 2-1 2-2 " + ) + ); + + test_style!( + Style::empty() + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) + .top('+'), + static_table!( + "+++++++++++++++++++++++++++++++++" + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty() + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) + .bottom('+'), + static_table!( + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + "+++++++++++++++++++++++++++++++++" + ) + ); + test_style!( + Style::empty() + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) + .left('+'), + static_table!( + "+ N column 0 column 1 column 2 " + "+---------------------------------" + "+ 0 0-0 0-1 0-2 " + "+ 1 1-0 1-1 1-2 " + "+ 2 2-0 2-1 2-2 " + ) + ); + test_style!( + Style::empty() + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) + .right('+'), + static_table!( + " N column 0 column 1 column 2 +" + "---------------------------------+" + " 0 0-0 0-1 0-2 +" + " 1 1-0 1-1 1-2 +" + " 2 2-0 2-1 2-2 +" + ) + ); + test_style!( + Style::empty() + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) + .vertical('+'), + static_table!( + " N + column 0 + column 1 + column 2 " + "---+----------+----------+----------" + " 0 + 0-0 + 0-1 + 0-2 " + " 1 + 1-0 + 1-1 + 1-2 " + " 2 + 2-0 + 2-1 + 2-2 " + ) + ); + test_style!( + Style::empty() + .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) + .horizontal('+'), + static_table!( + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + "+++++++++++++++++++++++++++++++++" + " 1 1-0 1-1 1-2 " + "+++++++++++++++++++++++++++++++++" + " 2 2-0 2-1 2-2 " + ) + ); + + // Full + + test_style!( + Style::empty() + .top('-') + .bottom('+') + .left('|') + .right('*') + .horizontal('x') + .horizontals(vec![HorizontalLine::new(1, Line::filled('z'))]) + .vertical('#'), + static_table!( + "|---#----------#----------#----------*" + "| N # column 0 # column 1 # column 2 *" + "zzzz#zzzzzzzzzz#zzzzzzzzzz#zzzzzzzzzzz" + "| 0 # 0-0 # 0-1 # 0-2 *" + "xxxx#xxxxxxxxxx#xxxxxxxxxx#xxxxxxxxxxx" + "| 1 # 1-0 # 1-1 # 1-2 *" + "xxxx#xxxxxxxxxx#xxxxxxxxxx#xxxxxxxxxxx" + "| 2 # 2-0 # 2-1 # 2-2 *" + "|+++#++++++++++#++++++++++#++++++++++*" + ), + ); + + let full_style = Style::empty() + .top('-') + .bottom('+') + .left('|') + .right('*') + .horizontal('x') + .horizontals(vec![HorizontalLine::new(1, Line::filled(','))]) + .vertical('#') + .intersection_bottom('@') + .intersection_top('!') + .intersection_left('=') + .intersection_right('$') + .intersection('+') + .corner_top_left(';') + .corner_bottom_left('?') + .corner_top_right('.') + .corner_bottom_right('%'); + test_style!( + full_style.clone(), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); + + // Overwrite intersections and corners + + test_style!( + full_style.clone().top('q'), + static_table!( + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); + test_style!( + full_style.clone().bottom('q'), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + ) + ); + test_style!( + full_style.clone().left('w'), + static_table!( + "w---!----------!----------!----------." + "w N # column 0 # column 1 # column 2 *" + "w,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "w 0 # 0-0 # 0-1 # 0-2 *" + "wxxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "w 1 # 1-0 # 1-1 # 1-2 *" + "wxxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "w 2 # 2-0 # 2-1 # 2-2 *" + "w+++@++++++++++@++++++++++@++++++++++%" + ) + ); + test_style!( + full_style.clone().right('i'), + static_table!( + ";---!----------!----------!----------i" + "| N # column 0 # column 1 # column 2 i" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,i" + "| 0 # 0-0 # 0-1 # 0-2 i" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxxi" + "| 1 # 1-0 # 1-1 # 1-2 i" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxxi" + "| 2 # 2-0 # 2-1 # 2-2 i" + "?+++@++++++++++@++++++++++@++++++++++i" + ) + ); + test_style!( + full_style.clone().horizontal('q'), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "| 1 # 1-0 # 1-1 # 1-2 *" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); + test_style!( + full_style.clone().vertical('q'), + static_table!( + ";---q----------q----------q----------." + "| N q column 0 q column 1 q column 2 *" + ",,,,q,,,,,,,,,,q,,,,,,,,,,q,,,,,,,,,,," + "| 0 q 0-0 q 0-1 q 0-2 *" + "=xxxqxxxxxxxxxxqxxxxxxxxxxqxxxxxxxxxx$" + "| 1 q 1-0 q 1-1 q 1-2 *" + "=xxxqxxxxxxxxxxqxxxxxxxxxxqxxxxxxxxxx$" + "| 2 q 2-0 q 2-1 q 2-2 *" + "?+++q++++++++++q++++++++++q++++++++++%" + ) + ); + test_style!( + full_style + .clone() + .horizontals(vec![HorizontalLine::new(1, Line::filled('q'))]), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); + + // Turn off borders + + let empty_table = static_table!( + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ); + test_style!(Style::empty().top('-').remove_top(), empty_table); + test_style!(Style::empty().bottom('-').remove_bottom(), empty_table); + test_style!(Style::empty().right('-').remove_right(), empty_table); + test_style!(Style::empty().left('-').remove_left(), empty_table); + test_style!( + Style::empty().horizontal('-').remove_horizontal(), + empty_table + ); + test_style!(Style::empty().vertical('-').remove_vertical(), empty_table); + test_style!( + Style::empty().horizontals(vec![HorizontalLine::new( + 1, + Line::new(Some('-'), None, None, None) + )]), + static_table!( + " N column 0 column 1 column 2 " + "---------------------------------" + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + ); + + test_style!( + full_style.clone().remove_top(), + static_table!( + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); + test_style!( + full_style.clone().remove_bottom(), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + ) + ); + test_style!( + full_style.clone().remove_right(), + static_table!( + ";---!----------!----------!----------" + "| N # column 0 # column 1 # column 2 " + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 " + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx" + "| 1 # 1-0 # 1-1 # 1-2 " + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx" + "| 2 # 2-0 # 2-1 # 2-2 " + "?+++@++++++++++@++++++++++@++++++++++" + ) + ); + test_style!( + full_style.clone().remove_left(), + static_table!( + "---!----------!----------!----------." + " N # column 0 # column 1 # column 2 *" + ",,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + " 0 # 0-0 # 0-1 # 0-2 *" + "xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + " 1 # 1-0 # 1-1 # 1-2 *" + "xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + " 2 # 2-0 # 2-1 # 2-2 *" + "+++@++++++++++@++++++++++@++++++++++%" + ) + ); + test_style!( + full_style.clone().remove_horizontal(), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," + "| 0 # 0-0 # 0-1 # 0-2 *" + "| 1 # 1-0 # 1-1 # 1-2 *" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); + test_style!( + full_style.clone().remove_vertical(), + static_table!( + ";---------------------------------." + "| N column 0 column 1 column 2 *" + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + "| 0 0-0 0-1 0-2 *" + "=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx$" + "| 1 1-0 1-1 1-2 *" + "=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx$" + "| 2 2-0 2-1 2-2 *" + "?+++++++++++++++++++++++++++++++++%" + ) + ); + test_style!( + full_style.remove_horizontals(), + static_table!( + ";---!----------!----------!----------." + "| N # column 0 # column 1 # column 2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 0 # 0-0 # 0-1 # 0-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 1 # 1-0 # 1-1 # 1-2 *" + "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" + "| 2 # 2-0 # 2-1 # 2-2 *" + "?+++@++++++++++@++++++++++@++++++++++%" + ) + ); +} + +#[test] +fn test_default_border_usage() { + macro_rules! test_border { + ($modify:expr, $expected:expr) => { + let table = Matrix::new(3, 3) + .insert((1, 1), "a longer string") + .with(Style::empty()) + .with($modify) + .to_string(); + + assert_eq!(table, $expected); + }; + } + + test_border! { + Modify::new((3, 2)).with(Border::default().corner_bottom_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + " * " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().corner_bottom_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + " * " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().bottom('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + " ********** " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().bottom('*').corner_bottom_left('#')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + " #********** " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().bottom('*').corner_bottom_right('#')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + " **********# " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 * 2-1 2-2 " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().corner_top_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().left('#').corner_top_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 # 2-1 2-2 " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().left('#').corner_bottom_left('@').corner_top_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 # 2-1 2-2 " + " @ " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().right('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 * 2-2 " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().corner_top_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().right('#').corner_top_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 2-1 # 2-2 " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().right('#').corner_top_right('*').corner_bottom_right('@')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 2-1 # 2-2 " + " @ " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::default().right('#').corner_top_right('*').corner_bottom_left('@')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " * " + " 2 2-0 2-1 # 2-2 " + " @ " + ) + } + test_border! { + Modify::new((3, 2)).with(Border::filled('@')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " @@@@@@@@@@@@ " + " 2 2-0 @ 2-1 @ 2-2 " + " @@@@@@@@@@@@ " + ) + } + + test_border! { + Modify::new((1, 2)).with(Border::default().corner_bottom_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " * " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().corner_bottom_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " * " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().bottom('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " ********** " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().bottom('*').corner_bottom_left('#')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " #********** " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().bottom('*').corner_bottom_right('#')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " **********# " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().left('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string * 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().corner_top_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().left('#').corner_top_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string # 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().left('#').corner_bottom_left('@').corner_top_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string # 0-1 0-2 " + " @ " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().right('*')), + static_table!( + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 * 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().corner_top_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().right('#').corner_top_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string 0-1 # 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().right('#').corner_top_right('*').corner_bottom_right('@')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string 0-1 # 0-2 " + " @ " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::default().right('#').corner_top_right('*').corner_bottom_left('@')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string 0-1 # 0-2 " + " @ " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((1, 2)).with(Border::filled('@')), + static_table!( + " N column 0 column 1 column 2 " + " @@@@@@@@@@@@ " + " 0 a longer string @ 0-1 @ 0-2 " + " @@@@@@@@@@@@ " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + + test_border! { + Modify::new((0, 3)).with(Border::default().corner_bottom_left('*')), + static_table!( + " N column 0 column 1 column 2 " + " * " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().corner_bottom_right('*')), + static_table!( + " N column 0 column 1 column 2 " + " *" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().bottom('*')), + static_table!( + " N column 0 column 1 column 2 " + " **********" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().bottom('*').corner_bottom_left('#')), + static_table!( + " N column 0 column 1 column 2 " + " #**********" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().bottom('*').corner_bottom_right('#')), + static_table!( + " N column 0 column 1 column 2 " + " **********#" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().left('*')), + static_table!( + " N column 0 column 1 * column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().corner_top_left('*')), + static_table!( + " * " + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().left('#').corner_top_left('*')), + static_table!( + " * " + " N column 0 column 1 # column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().left('#').corner_bottom_left('@').corner_top_left('*')), + static_table!( + " * " + " N column 0 column 1 # column 2 " + " @ " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().right('*')), + static_table!( + " N column 0 column 1 column 2 *" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().corner_top_right('*')), + static_table!( + " *" + " N column 0 column 1 column 2 " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().right('#').corner_top_right('*')), + static_table!( + " *" + " N column 0 column 1 column 2 #" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().right('#').corner_top_right('*').corner_bottom_right('@')), + static_table!( + " *" + " N column 0 column 1 column 2 #" + " @" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::default().right('#').corner_top_right('*').corner_bottom_left('@')), + static_table!( + " *" + " N column 0 column 1 column 2 #" + " @ " + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } + test_border! { + Modify::new((0, 3)).with(Border::filled('@')), + static_table!( + " @@@@@@@@@@@@" + " N column 0 column 1 @ column 2 @" + " @@@@@@@@@@@@" + " 0 a longer string 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + ) + } +} + +#[cfg(feature = "color")] +#[test] +fn border_colored_test() { + let table = Matrix::table(2, 2) + .with(Style::ascii()) + .with( + Modify::new(Rows::single(1)) + .with( + BorderColor::filled(Color::try_from('*'.blue().to_string()).unwrap()) + .top(Color::try_from('#'.truecolor(12, 220, 100).to_string()).unwrap()), + ) + .with(Border::filled('*').top('#')), + ) + .to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "*###*##########*##########*" + "* 0 * 0-0 * 0-1 *" + "***************************" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" + ) + ); + + assert_eq!( + table, + static_table!( + "+---+----------+----------+" + "| N | column 0 | column 1 |" + "\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m###\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m" + "\u{1b}[34m*\u{1b}[39m 0 \u{1b}[34m*\u{1b}[39m 0-0 \u{1b}[34m*\u{1b}[39m 0-1 \u{1b}[34m*\u{1b}[39m" + "\u{1b}[34m***************************\u{1b}[39m" + "| 1 | 1-0 | 1-1 |" + "+---+----------+----------+" + ) + ); + + let table = Matrix::table(2, 2) + .with(Style::empty()) + .with( + Modify::new(Rows::single(1)) + .with( + BorderColor::filled(Color::try_from('*'.blue().to_string()).unwrap()) + .top(Color::try_from('#'.truecolor(12, 220, 100).to_string()).unwrap()), + ) + .with(Border::filled('*').top('#')), + ) + .to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + " N column 0 column 1 " + "*###*##########*##########*" + "* 0 * 0-0 * 0-1 *" + "***************************" + " 1 1-0 1-1 " + ) + ); + + assert_eq!( + table, + " N column 0 column 1 \n\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m###\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m\n\u{1b}[34m*\u{1b}[39m 0 \u{1b}[34m*\u{1b}[39m 0-0 \u{1b}[34m*\u{1b}[39m 0-1 \u{1b}[34m*\u{1b}[39m\n\u{1b}[34m***************************\u{1b}[39m\n 1 1-0 1-1 ", + ); +} + +#[cfg(feature = "color")] +#[test] +fn style_with_color_test() { + let mut style: RawStyle = Style::ascii().into(); + style + .set_left(Some('[')) + .set_right(Some(']')) + .set_top(Some('-')) + .set_bottom(Some('-')) + .set_vertical(Some('|')) + .set_intersection(Some('+')); + style + .set_color_left(Color::FG_RED) + .set_color_right(Color::FG_RED) + .set_color_top(Color::FG_BLUE) + .set_color_bottom(Color::FG_BLUE) + .set_color_vertical(Color::FG_YELLOW) + .set_color_intersection(Color::try_from(' '.purple().to_string()).unwrap()); + + let table = Matrix::new(3, 3).with(style).to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + "+---+----------+----------+----------+" + "[ N | column 0 | column 1 | column 2 ]" + "+---+----------+----------+----------+" + "[ 0 | 0-0 | 0-1 | 0-2 ]" + "+---+----------+----------+----------+" + "[ 1 | 1-0 | 1-1 | 1-2 ]" + "+---+----------+----------+----------+" + "[ 2 | 2-0 | 2-1 | 2-2 ]" + "+---+----------+----------+----------+" + ) + ); + + assert_eq!(table, "+\u{1b}[34m---\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\n\u{1b}[31m[\u{1b}[39m N \u{1b}[33m|\u{1b}[39m column 0 \u{1b}[33m|\u{1b}[39m column 1 \u{1b}[33m|\u{1b}[39m column 2 \u{1b}[31m]\u{1b}[39m\n+---\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------+\n\u{1b}[31m[\u{1b}[39m 0 \u{1b}[33m|\u{1b}[39m 0-0 \u{1b}[33m|\u{1b}[39m 0-1 \u{1b}[33m|\u{1b}[39m 0-2 \u{1b}[31m]\u{1b}[39m\n+---\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------+\n\u{1b}[31m[\u{1b}[39m 1 \u{1b}[33m|\u{1b}[39m 1-0 \u{1b}[33m|\u{1b}[39m 1-1 \u{1b}[33m|\u{1b}[39m 1-2 \u{1b}[31m]\u{1b}[39m\n+---\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------+\n\u{1b}[31m[\u{1b}[39m 2 \u{1b}[33m|\u{1b}[39m 2-0 \u{1b}[33m|\u{1b}[39m 2-1 \u{1b}[33m|\u{1b}[39m 2-2 \u{1b}[31m]\u{1b}[39m\n+\u{1b}[34m---\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+"); +} + +test_table!( + empty_line_clears_lines, + Matrix::new(3, 3).with(Style::rounded().remove_horizontals()), + "╭───┬──────────┬──────────┬──────────╮" + "│ N │ column 0 │ column 1 │ column 2 │" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "╰───┴──────────┴──────────┴──────────╯" +); + +test_table!( + empty_line_clears_lines_1, + Matrix::new(3, 3).with(Style::rounded().remove_horizontals()), + "╭───┬──────────┬──────────┬──────────╮" + "│ N │ column 0 │ column 1 │ column 2 │" + "│ 0 │ 0-0 │ 0-1 │ 0-2 │" + "│ 1 │ 1-0 │ 1-1 │ 1-2 │" + "│ 2 │ 2-0 │ 2-1 │ 2-2 │" + "╰───┴──────────┴──────────┴──────────╯" +); + +test_table!( + border_color, + { + use tabled::settings::Color; + Matrix::new(3, 3).with(Style::psql()).with(Color::BG_GREEN) + }, + " \u{1b}[42mN\u{1b}[49m | \u{1b}[42mcolumn 0\u{1b}[49m | \u{1b}[42mcolumn 1\u{1b}[49m | \u{1b}[42mcolumn 2\u{1b}[49m \n---+----------+----------+----------\n \u{1b}[42m0\u{1b}[49m | \u{1b}[42m0-0\u{1b}[49m | \u{1b}[42m0-1\u{1b}[49m | \u{1b}[42m0-2\u{1b}[49m \n \u{1b}[42m1\u{1b}[49m | \u{1b}[42m1-0\u{1b}[49m | \u{1b}[42m1-1\u{1b}[49m | \u{1b}[42m1-2\u{1b}[49m \n \u{1b}[42m2\u{1b}[49m | \u{1b}[42m2-0\u{1b}[49m | \u{1b}[42m2-1\u{1b}[49m | \u{1b}[42m2-2\u{1b}[49m " +); + +test_table!( + text_color, + { + use tabled::settings::Color; + Matrix::new(3, 3).with(Style::psql()).with(Modify::new(Segment::all()).with(Color::BG_BLACK)) + }, + " \u{1b}[40mN\u{1b}[49m | \u{1b}[40mcolumn 0\u{1b}[49m | \u{1b}[40mcolumn 1\u{1b}[49m | \u{1b}[40mcolumn 2\u{1b}[49m \n---+----------+----------+----------\n \u{1b}[40m0\u{1b}[49m | \u{1b}[40m0-0\u{1b}[49m | \u{1b}[40m0-1\u{1b}[49m | \u{1b}[40m0-2\u{1b}[49m \n \u{1b}[40m1\u{1b}[49m | \u{1b}[40m1-0\u{1b}[49m | \u{1b}[40m1-1\u{1b}[49m | \u{1b}[40m1-2\u{1b}[49m \n \u{1b}[40m2\u{1b}[49m | \u{1b}[40m2-0\u{1b}[49m | \u{1b}[40m2-1\u{1b}[49m | \u{1b}[40m2-2\u{1b}[49m " +); + +test_table!( + verticals_0, + Matrix::new(3, 3) + .with(Style::rounded().verticals(vec![VerticalLine::new(0, Line::filled('+')), VerticalLine::new(4, Line::filled('+'))])), + "+───┬──────────┬──────────┬──────────+" + "+ N │ column 0 │ column 1 │ column 2 +" + "├───┼──────────┼──────────┼──────────┤" + "+ 0 │ 0-0 │ 0-1 │ 0-2 +" + "+ 1 │ 1-0 │ 1-1 │ 1-2 +" + "+ 2 │ 2-0 │ 2-1 │ 2-2 +" + "+───┴──────────┴──────────┴──────────+" +); + +test_table!( + verticals_1, + Matrix::new(3, 3) + .with(Style::rounded().verticals((1..4).map(|i| VerticalLine::new(i, Line::filled('+'))))), + "╭───+──────────+──────────+──────────╮" + "│ N + column 0 + column 1 + column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 + 0-0 + 0-1 + 0-2 │" + "│ 1 + 1-0 + 1-1 + 1-2 │" + "│ 2 + 2-0 + 2-1 + 2-2 │" + "╰───+──────────+──────────+──────────╯" +); + +test_table!( + verticals_2, + Matrix::new(3, 3).with(Style::rounded().verticals(vec![VerticalLine::new(1, Line::filled('+'))])), + "╭───+──────────┬──────────┬──────────╮" + "│ N + column 0 │ column 1 │ column 2 │" + "├───┼──────────┼──────────┼──────────┤" + "│ 0 + 0-0 │ 0-1 │ 0-2 │" + "│ 1 + 1-0 │ 1-1 │ 1-2 │" + "│ 2 + 2-0 │ 2-1 │ 2-2 │" + "╰───+──────────┴──────────┴──────────╯" +); + +test_table!( + verticals_3, + Matrix::new(3, 3).with(Style::ascii().verticals([VerticalLine::new(1, Line::filled('*'))])), + "+---*----------+----------+----------+" + "| N * column 0 | column 1 | column 2 |" + "+---*----------+----------+----------+" + "| 0 * 0-0 | 0-1 | 0-2 |" + "+---*----------+----------+----------+" + "| 1 * 1-0 | 1-1 | 1-2 |" + "+---*----------+----------+----------+" + "| 2 * 2-0 | 2-1 | 2-2 |" + "+---*----------+----------+----------+" +); + +test_table!( + verticals_4, + Matrix::new(3, 3).with(Style::ascii().verticals((0..10).map(|i| VerticalLine::new(i, Line::new(Some('*'), Some('x'), Some('c'), Some('2')))))), + "c---c----------c----------c----------c" + "* N * column 0 * column 1 * column 2 *" + "x---x----------x----------x----------x" + "* 0 * 0-0 * 0-1 * 0-2 *" + "x---x----------x----------x----------x" + "* 1 * 1-0 * 1-1 * 1-2 *" + "x---x----------x----------x----------x" + "* 2 * 2-0 * 2-1 * 2-2 *" + "2---2----------2----------2----------2" +); + +test_table!( + vertical_line_0, + Matrix::new(3, 3) + .with(HorizontalLine::new(1, Line::new(Some('8'), Some('8'), Some('8'), Some('8')))) + .with(VerticalLine::new(1, Line::new(Some('*'), Some('x'), Some('c'), Some('2')))), + "+---c----------+----------+----------+" + "| N * column 0 | column 1 | column 2 |" + "88888888888888888888888888888888888888" + "| 0 * 0-0 | 0-1 | 0-2 |" + "+---x----------+----------+----------+" + "| 1 * 1-0 | 1-1 | 1-2 |" + "+---x----------+----------+----------+" + "| 2 * 2-0 | 2-1 | 2-2 |" + "+---2----------+----------+----------+" +); + +test_table!( + vertical_line_1, + Matrix::new(3, 3) + .with(Style::empty()) + .with(VerticalLine::new(1, Line::new(Some('*'), Some('x'), Some('c'), Some('2')))), + " c " + " N * column 0 column 1 column 2 " + " 0 * 0-0 0-1 0-2 " + " 1 * 1-0 1-1 1-2 " + " 2 * 2-0 2-1 2-2 " + " 2 " +); + +test_table!( + vertical_line_2, + Matrix::new(3, 3) + .with(Style::empty()) + .with(VerticalLine::new(1, Line::new(None, Some('x'), Some('c'), Some('2')))), + " c " + " N column 0 column 1 column 2 " + " 0 0-0 0-1 0-2 " + " 1 1-0 1-1 1-2 " + " 2 2-0 2-1 2-2 " + " 2 " +); + +test_table!( + vertical_line_3, + Matrix::new(3, 3) + .with(Style::empty()) + .with(VerticalLine::new(1, Line::new(Some('*'), Some('x'), None, None))), + " N * column 0 column 1 column 2 " + " 0 * 0-0 0-1 0-2 " + " 1 * 1-0 1-1 1-2 " + " 2 * 2-0 2-1 2-2 " +); + +test_table!( + override_horizontal_border_on_line, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::single(1)) + .with(BorderChar::horizontal(':', Offset::Begin(0))) + .with(BorderChar::horizontal(':', Offset::End(0))) + ), + "| N | column 0 | column 1 | column 2 |" + "|:-:|:--------:|:--------:|:--------:|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + override_horizontal_border_on_borders, + Matrix::new(3, 3) + .with(Modify::new(Rows::new(..5)) + .with(BorderChar::horizontal(':', Offset::Begin(0))) + .with(BorderChar::horizontal('y', Offset::Begin(3))) + .with(BorderChar::horizontal(':', Offset::End(0))) + .with(BorderChar::horizontal('x', Offset::End(3))) + ), + "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" + "| N | column 0 | column 1 | column 2 |" + "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" + "| 0 | 0-0 | 0-1 | 0-2 |" + "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" + "| 1 | 1-0 | 1-1 | 1-2 |" + "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" + "| 2 | 2-0 | 2-1 | 2-2 |" + "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" +); + +test_table!( + override_horizontal_border_on_border, + Matrix::new(3, 3) + .with(Modify::new(Rows::new(..5)) + .with(Border::filled('[')) + .with(BorderChar::horizontal(':', Offset::Begin(0))) + .with(BorderChar::horizontal('y', Offset::Begin(3))) + .with(BorderChar::horizontal(':', Offset::End(0))) + .with(BorderChar::horizontal('x', Offset::End(3))) + ), + "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" + "[ N [ column 0 [ column 1 [ column 2 [" + "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" + "[ 0 [ 0-0 [ 0-1 [ 0-2 [" + "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" + "[ 1 [ 1-0 [ 1-1 [ 1-2 [" + "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" + "[ 2 [ 2-0 [ 2-1 [ 2-2 [" + "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" +); + +test_table!( + override_vertical_border_on_line, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::single(1)) + .with(BorderChar::vertical(':', Offset::Begin(0))) + ), + "| N : column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 : 0-0 | 0-1 | 0-2 |" + "| 1 : 1-0 | 1-1 | 1-2 |" + "| 2 : 2-0 | 2-1 | 2-2 |" +); + +test_table!( + override_vertical_border_on_line_1, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::single(1)) + .with(BorderChar::vertical(':', Offset::End(0))) + ), + "| N : column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 : 0-0 | 0-1 | 0-2 |" + "| 1 : 1-0 | 1-1 | 1-2 |" + "| 2 : 2-0 | 2-1 | 2-2 |" +); + +test_table!( + override_vertical_border_on_line_multiline, + Matrix::new(3, 3) + .with(Modify::new(Rows::single(1)).with(Format::content(|s| format!("\nsome text\ntext\n{s}\ntext\ntext\n")))) + .with(Style::markdown()) + .with(Modify::new(Columns::single(1)) + .with(BorderChar::vertical(':', Offset::Begin(4))) + ), + "| N | column 0 | column 1 | column 2 |" + "|-----------|-----------|-----------|-----------|" + "| | | | |" + "| some text | some text | some text | some text |" + "| text | text | text | text |" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| text : text | text | text |" + "| text | text | text | text |" + "| | | | |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + override_vertical_border_on_line_multiline_2, + Matrix::new(3, 3) + .with(Modify::new(Rows::single(1)).with(Format::content(|s| format!("\nsome text\ntext\n{s}\ntext\ntext\n")))) + .with(Style::markdown()) + .with(Modify::new(Columns::single(1)) + .with(BorderChar::vertical(':', Offset::End(4))) + ), + "| N | column 0 | column 1 | column 2 |" + "|-----------|-----------|-----------|-----------|" + "| | | | |" + "| some text | some text | some text | some text |" + "| text : text | text | text |" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| text | text | text | text |" + "| text | text | text | text |" + "| | | | |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + override_vertical_and_horizontal_border_on_line, + Matrix::new(3, 3) + .with(Modify::new(Rows::single(1)).with(Format::content(|s| format!("\nsome text\ntext\n{s}\ntext\ntext\n")))) + .with(Style::markdown()) + .with(Modify::new(Columns::new(..5)) + .with(BorderChar::vertical('y', Offset::Begin(0))) + .with(BorderChar::vertical('^', Offset::End(0))) + ) + .with(Modify::new(Rows::single(1)) + .with(BorderChar::horizontal('x', Offset::Begin(0))) + .with(BorderChar::horizontal('@', Offset::End(0))) + ), + "y N y column 0 y column 1 y column 2 y" + "|x---------@|x---------@|x---------@|x---------@|" + "y y y y y" + "| some text | some text | some text | some text |" + "| text | text | text | text |" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| text | text | text | text |" + "| text | text | text | text |" + "^ ^ ^ ^ ^" + "y 1 y 1-0 y 1-1 y 1-2 y" + "y 2 y 2-0 y 2-1 y 2-2 y" +); + +test_table!( + table_format_alignment_left_test, + format!("{:<}", Table::new(vec!["hello", "world", "!"])), + "+-------+" + "| &str |" + "+-------+" + "| hello |" + "+-------+" + "| world |" + "+-------+" + "| ! |" + "+-------+" +); + +test_table!( + table_format_alignment_right_test, + format!("{:>}", Table::new(vec!["hello", "world", "!"])), + "+-------+" + "| &str |" + "+-------+" + "| hello |" + "+-------+" + "| world |" + "+-------+" + "| ! |" + "+-------+" +); + +test_table!( + table_format_alignment_center_test, + format!("{:^}", Table::new(vec!["hello", "world", "!"])), + "+-------+" + "| &str |" + "+-------+" + "| hello |" + "+-------+" + "| world |" + "+-------+" + "| ! |" + "+-------+" +); + +test_table!( + table_format_width_0_test, + format!("{:<13}", Table::new(vec!["hello", "world", "!"])), + " +-------+" + " | &str |" + " +-------+" + " | hello |" + " +-------+" + " | world |" + " +-------+" + " | ! |" + " +-------+" +); + +test_table!( + table_format_width_1_test, + format!("{:>13}", Table::new(vec!["hello", "world", "!"])), + "+-------+ " + "| &str | " + "+-------+ " + "| hello | " + "+-------+ " + "| world | " + "+-------+ " + "| ! | " + "+-------+ " +); + +test_table!( + table_format_width_2_test, + format!("{:^13}", Table::new(vec!["hello", "world", "!"])), + " +-------+ " + " | &str | " + " +-------+ " + " | hello | " + " +-------+ " + " | world | " + " +-------+ " + " | ! | " + " +-------+ " +); + +test_table!( + table_format_width_3_test, + format!("{:x^13}", Table::new(vec!["hello", "world", "!"])), + "xx+-------+xx" + "xx| &str |xx" + "xx+-------+xx" + "xx| hello |xx" + "xx+-------+xx" + "xx| world |xx" + "xx+-------+xx" + "xx| ! |xx" + "xx+-------+xx" +); + +test_table!( + table_format_width_4_test, + format!("{:x<13}", Table::new(vec!["hello", "world", "!"])), + "xxxx+-------+" + "xxxx| &str |" + "xxxx+-------+" + "xxxx| hello |" + "xxxx+-------+" + "xxxx| world |" + "xxxx+-------+" + "xxxx| ! |" + "xxxx+-------+" +); + +test_table!( + table_format_width_5_test, + format!("{:x>13}", Table::new(vec!["hello", "world", "!"])), + "+-------+xxxx" + "| &str |xxxx" + "+-------+xxxx" + "| hello |xxxx" + "+-------+xxxx" + "| world |xxxx" + "+-------+xxxx" + "| ! |xxxx" + "+-------+xxxx" +); + +test_table!( + table_style_no_bottom_no_new_line, + Matrix::table(0, 0).with(Style::markdown().remove_horizontals()), + "| N |" +); diff --git a/vendor/tabled/tests/settings/width_test.rs b/vendor/tabled/tests/settings/width_test.rs new file mode 100644 index 000000000..d2bc84a46 --- /dev/null +++ b/vendor/tabled/tests/settings/width_test.rs @@ -0,0 +1,2836 @@ +#![cfg(feature = "std")] + +use tabled::{ + grid::util::string::string_width_multiline, + settings::{ + formatting::{TabSize, TrimStrategy}, + object::{Columns, Object, Rows, Segment}, + peaker::{PriorityMax, PriorityMin}, + width::{Justify, MinWidth, SuffixLimit, Width}, + Alignment, Margin, Modify, Padding, Panel, Settings, Span, Style, + }, +}; + +use crate::matrix::Matrix; +use testing_table::{is_lines_equal, static_table, test_table}; + +#[cfg(feature = "color")] +use ::{ansi_str::AnsiStr, owo_colors::OwoColorize}; + +#[cfg(all(feature = "derive", feature = "color"))] +use ::owo_colors::AnsiColors; + +test_table!( + max_width, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::truncate(1))), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0 | 0 | 0 |" + "| 1 | 1 | 1 | 1 |" + "| 2 | 2 | 2 | 2 |" +); + +test_table!( + max_width_with_suffix, + Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Columns::new(1..).not(Rows::single(0))) + .with(Width::truncate(2).suffix("...")), + ), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | .. | .. | .. |" + "| 1 | .. | .. | .. |" + "| 2 | .. | .. | .. |" +); + +test_table!( + max_width_doesnt_icrease_width_if_it_is_smaller, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::truncate(50))), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + max_width_wrapped, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::wrap(2))), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0- | 0- | 0- |" + "| | 0 | 1 | 2 |" + "| 1 | 1- | 1- | 1- |" + "| | 0 | 1 | 2 |" + "| 2 | 2- | 2- | 2- |" + "| | 0 | 1 | 2 |" +); + +test_table!( + max_width_wrapped_does_nothing_if_str_is_smaller, + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::wrap(100))), + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" +); + +test_table!( + max_width_wrapped_keep_words_0, + { + let table = Matrix::iter(vec!["this is a long sentence"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + assert!(is_lines_equal(&table, 17 + 2 + 2)); + + table + }, + "| &str |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +test_table!( + max_width_wrapped_keep_words_1, + { + let table = Matrix::iter(vec!["this is a long sentence"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + assert!(is_lines_equal(&table, 17 + 2 + 2)); + + table + }, + "| &str |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +test_table!( + max_width_wrapped_keep_words_2, + { + let table = Matrix::iter(vec!["this is a long sentence"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + assert!(is_lines_equal(&table, 17 + 2 + 2)); + + table + }, + "| &str |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_3, + { + let table = Matrix::iter(vec!["this is a long sentence"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + assert!(is_lines_equal(&table, 17 + 2 + 2)); + + table + }, + // 'sentence' doesn't have a space ' sentence' because we use left alignment + "| &str |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +#[cfg(not(feature = "color"))] +test_table!( + max_width_wrapped_keep_words_3, + { + let table = Matrix::iter(vec!["this is a long sentence"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + assert!(is_lines_equal(&table, 17 + 2 + 2)); + + table + }, + // 'sentence' doesn't have a space ' sentence' because we use left alignment + "| &str |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +test_table!( + max_width_wrapped_keep_words_4, + { + let table = Matrix::iter(vec!["this"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words())) + .to_string(); + + assert!(is_lines_equal(&table, 8)); + + table + }, + "| &str |" + "|------|" + "| this |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_0, + { + let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + AnsiStr::ansi_strip(&table).to_string() + }, + "| String |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_0_1, + Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), + "| String |" + "|-------------------|" + "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" + "| \u{1b}[32m\u{1b}[40msentence\u{1b}[39m\u{1b}[49m |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_1, + { + let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + AnsiStr::ansi_strip(&table).to_string() + }, + "| String |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_1_1, + Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), + "| String |" + "|-------------------|" + "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" + "| \u{1b}[32m\u{1b}[40msentence\u{1b}[39m\u{1b}[49m |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_2, + { + let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + AnsiStr::ansi_strip(&table).to_string() + }, + "| String |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_2_1, + Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), + "| String |" + "|-------------------|" + "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" + "| \u{1b}[32m\u{1b}[40msentence\u{1b}[39m\u{1b}[49m |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_3, + { + let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + AnsiStr::ansi_strip(&table).to_string() + }, + "| String |" + "|-------------------|" + "| this is a long |" + "| sentence |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_3_1, + Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), + "| String |" + "|-------------------|" + "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" + "| \u{1b}[32m\u{1b}[40m sentence\u{1b}[39m\u{1b}[49m |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_4, + { + let table = Matrix::iter(vec!["this".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words())) + .to_string(); + + AnsiStr::ansi_strip(&table).to_string() + }, + "| String |" + "|--------|" + "| this |" +); + +#[cfg(feature = "color")] +test_table!( + max_width_wrapped_keep_words_color_4_1, + Matrix::iter(vec!["this".on_black().green().to_string()]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words())), + "| String |" + "|--------|" + "| \u{1b}[32;40mthis\u{1b}[0m |" +); + +test_table!( + max_width_wrapped_keep_words_long_word, + Matrix::iter(["this is a long sentencesentencesentence"]) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), + "| &str |" + "|-------------------|" + "| this is a long se |" + "| ntencesentencesen |" + "| tence |" +); + +#[cfg(feature = "color")] +#[test] +fn max_width_wrapped_keep_words_long_word_color() { + let data = vec!["this is a long sentencesentencesentence" + .on_black() + .green() + .to_string()]; + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) + .to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + "| String |" + "|-------------------|" + "| this is a long se |" + "| ntencesentencesen |" + "| tence |" + ) + ); + + assert_eq!( + table, + static_table!( + "| String |" + "|-------------------|" + "| \u{1b}[32m\u{1b}[40mthis is a long se\u{1b}[39m\u{1b}[49m |" + "| \u{1b}[32m\u{1b}[40mntencesentencesen\u{1b}[39m\u{1b}[49m |" + "| \u{1b}[32m\u{1b}[40mtence\u{1b}[39m\u{1b}[49m |" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn max_width_keep_words_1() { + use tabled::settings::style::HorizontalLine; + + let table = Matrix::iter(["asdf"]) + .with(Width::wrap(7).keep_words()) + .to_string(); + + assert_eq!( + table, + static_table!( + "+-----+" + "| &st |" + "| r |" + "+-----+" + "| asd |" + "| f |" + "+-----+" + ) + ); + + let table = Matrix::iter(["qweqw eqwe"]) + .with(Width::wrap(8).keep_words()) + .to_string(); + + assert_eq!( + table, + static_table!( + "+------+" + "| &str |" + "+------+" + "| qweq |" + "| w |" + "| eqwe |" + "+------+" + ) + ); + + let table = Matrix::iter([ + ["123 45678", "qweqw eqwe", "..."], + ["0", "1", "..."], + ["0", "1", "..."], + ]) + .with( + Style::modern() + .remove_horizontal() + .horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())]), + ) + .with(Width::wrap(21).keep_words().priority::<PriorityMax>()) + .with(Alignment::center()) + .to_string(); + + assert_eq!( + table, + static_table!( + "┌──────┬──────┬─────┐" + "│ 0 │ 1 │ 2 │" + "├──────┼──────┼─────┤" + "│ 123 │ qweq │ ... │" + "│ 4567 │ w │ │" + "│ 8 │ eqwe │ │" + "│ 0 │ 1 │ ... │" + "│ 0 │ 1 │ ... │" + "└──────┴──────┴─────┘" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn max_width_wrapped_collored() { + let data = &[ + "asd".red().to_string(), + "zxc2".blue().to_string(), + "asdasd".on_black().green().to_string(), + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::wrap(2))) + .to_string(); + + assert_eq!( + table, + "| St |\n| ri |\n| ng |\n|----|\n| \u{1b}[31mas\u{1b}[39m |\n| \u{1b}[31md\u{1b}[39m |\n| \u{1b}[34mzx\u{1b}[39m |\n| \u{1b}[34mc2\u{1b}[39m |\n| \u{1b}[32m\u{1b}[40mas\u{1b}[39m\u{1b}[49m |\n| \u{1b}[32m\u{1b}[40mda\u{1b}[39m\u{1b}[49m |\n| \u{1b}[32m\u{1b}[40msd\u{1b}[39m\u{1b}[49m |" + ); +} + +#[test] +fn dont_change_content_if_width_is_less_then_max_width() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::truncate(1000).suffix("..."))) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn max_width_with_emoji() { + let data = &["🤠", "😳🥵🥶😱😨", "🚴🏻♀️🚴🏻🚴🏻♂️🚵🏻♀️🚵🏻🚵🏻♂️"]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::truncate(6).suffix("..."))) + .to_string(); + + assert_eq!( + table, + static_table!( + "| &str |" + "|--------|" + "| 🤠 |" + "| 😳�... |" + "| 🚴�... |" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn color_chars_are_stripped() { + let data = &[ + "asd".red().to_string(), + "zxc".blue().to_string(), + "asdasd".on_black().green().to_string(), + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Width::truncate(4).suffix("..."))) + .to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + "| S... |" + "|------|" + "| asd |" + "| zxc |" + "| a... |" + ) + ); + + assert_eq!( + table, + "| S... |\n|------|\n| \u{1b}[31masd\u{1b}[39m |\n| \u{1b}[34mzxc\u{1b}[39m |\n| \u{1b}[32;40ma\u{1b}[39m\u{1b}[49m... |", + ); +} + +#[test] +fn min_width() { + let mut table = Matrix::table(3, 3); + table + .with(Style::markdown()) + .with(Modify::new(Rows::single(0)).with(MinWidth::new(12))); + + assert_eq!( + table.to_string(), + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|--------------|--------------|--------------|--------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ), + ); + + table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); + + assert_eq!( + table.to_string(), + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|--------------|--------------|--------------|--------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ), + ); +} + +#[test] +fn min_width_with_filler() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::single(0)).with(MinWidth::new(12).fill_with('.'))) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N........... | column 0.... | column 1.... | column 2.... |" + "|--------------|--------------|--------------|--------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn min_width_one_column() { + let mut table = Matrix::table(3, 3); + table + .with(Style::markdown()) + .with(Modify::new((0, 0)).with(MinWidth::new(5))); + + assert_eq!( + table.to_string(), + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|-------|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); + + table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); + + assert_eq!( + table.to_string(), + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|-------|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn min_width_on_smaller_content() { + assert_eq!( + Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Rows::single(0)).with(MinWidth::new(1))) + .to_string(), + Matrix::new(3, 3).with(Style::markdown()).to_string() + ); +} + +#[test] +fn min_with_max_width() { + let mut table = Matrix::table(3, 3); + table + .with(Style::markdown()) + .with(Modify::new(Rows::single(0)).with(MinWidth::new(3))) + .with(Modify::new(Rows::single(0)).with(Width::truncate(3))); + + assert_eq!( + table.to_string(), + static_table!( + "| N | col | col | col |" + "|-----|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); + + table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); + + assert_eq!( + table.to_string(), + static_table!( + "| N | col | col | col |" + "|-----|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn min_with_max_width_truncate_suffix() { + let mut table = Matrix::table(3, 3); + table + .with(Style::markdown()) + .with(Modify::new(Rows::single(0)).with(MinWidth::new(3))) + .with(Modify::new(Rows::single(0)).with(Width::truncate(3).suffix("..."))); + + assert_eq!( + table.to_string(), + static_table!( + "| N | ... | ... | ... |" + "|-----|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); + + table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); + + assert_eq!( + table.to_string(), + static_table!( + "| N | ... | ... | ... |" + "|-----|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn min_with_max_width_truncate_suffix_limit_replace() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Rows::single(0)).with( + Width::truncate(3) + .suffix("...") + .suffix_limit(SuffixLimit::Replace('x')), + ), + ) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | xxx | xxx | xxx |" + "|---|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn min_with_max_width_truncate_suffix_limit_cut() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Rows::single(0)).with( + Width::truncate(3) + .suffix("qwert") + .suffix_limit(SuffixLimit::Cut), + ), + ) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | qwe | qwe | qwe |" + "|---|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn min_with_max_width_truncate_suffix_limit_ignore() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with( + Modify::new(Rows::single(0)).with( + Width::truncate(3) + .suffix("qwert") + .suffix_limit(SuffixLimit::Ignore), + ), + ) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | col | col | col |" + "|---|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn min_with_max_width_truncate_suffix_try_color() { + let data = &[ + "asd".red().to_string(), + "zxc".blue().to_string(), + "asdasd".on_black().green().to_string(), + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Width::truncate(7).suffix("..").suffix_try_color(true)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| S.. |" + "|-----|" + "| \u{1b}[31masd\u{1b}[39m |" + "| \u{1b}[34mzxc\u{1b}[39m |" + "| \u{1b}[32;40ma\u{1b}[39m\u{1b}[49m\u{1b}[32m\u{1b}[40m..\u{1b}[39m\u{1b}[49m |" + ) + ); +} + +#[cfg(feature = "color")] +#[test] +fn min_width_color() { + let data = &[ + "asd".red().to_string(), + "zxc".blue().to_string(), + "asdasd".on_black().green().to_string(), + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(MinWidth::new(10))) + .to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + "| String |" + "|------------|" + "| asd |" + "| zxc |" + "| asdasd |" + ) + ); + + assert_eq!( + table, + "| String |\n|------------|\n| \u{1b}[31masd\u{1b}[39m |\n| \u{1b}[34mzxc\u{1b}[39m |\n| \u{1b}[32;40masdasd\u{1b}[0m |", + ); +} + +#[cfg(feature = "color")] +#[test] +fn min_width_color_with_smaller_then_width() { + let data = &[ + "asd".red().to_string(), + "zxc".blue().to_string(), + "asdasd".on_black().green().to_string(), + ]; + + assert_eq!( + Matrix::iter(data) + .with(Modify::new(Segment::all()).with(MinWidth::new(1))) + .to_string(), + Matrix::iter(data).to_string() + ); +} + +#[test] +fn total_width_big() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Width::truncate(80)) + .with(MinWidth::new(80)) + .to_string(); + + assert_eq!(string_width_multiline(&table), 80); + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|--------------|---------------------|--------------------|--------------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); + + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(TrimStrategy::None)) + .with(Settings::new(Width::truncate(80), Width::increase(80))) + .to_string(); + + assert_eq!(string_width_multiline(&table), 80); + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|--------------|---------------------|--------------------|--------------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn total_width_big_with_panel() { + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with( + Modify::new(Segment::all()) + .with(Alignment::center()) + .with(Padding::zero()), + ) + .with(Style::markdown()) + .with(Width::truncate(80)) + .with(MinWidth::new(80)) + .to_string(); + + assert!(is_lines_equal(&table, 80)); + assert_eq!( + table, + static_table!( + "| Hello World |" + "|--------------|---------------------|--------------------|--------------------|" + "| N | column 0 | column 1 | column 2 |" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn total_width_big_with_panel_with_wrapping_doesnt_affect_increase() { + let table1 = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::wrap(80)) + .with(MinWidth::new(80)) + .to_string(); + + let table2 = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(80)) + .with(MinWidth::new(80)) + .to_string(); + + assert_eq!(table1, table2); +} + +#[test] +fn total_width_small() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Width::truncate(14)) + .with(MinWidth::new(14)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | | | c |" + "|--|--|--|---|" + "| | | | 0 |" + "| | | | 1 |" + "| | | | 2 |" + ) + ); + assert!(is_lines_equal(&table, 14)); +} + +#[test] +fn total_width_smaller_then_content() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Width::truncate(8)) + .with(MinWidth::new(8)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | | | |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); +} + +#[test] +fn total_width_small_with_panel() { + let table = Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(20)) + .with(MinWidth::new(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | co | co | col |" + "|--|----|----|-----|" + "| | 0- | 0- | 0-2 |" + "| | 1- | 1- | 1-2 |" + "| | 2- | 2- | 2-2 |" + ) + ); + assert!(is_lines_equal(&table, 20)); + + let table = Matrix::iter(Vec::<usize>::new()) + .with(Panel::horizontal(0, "Hello World")) + .with( + Modify::new(Segment::all()) + .with(Alignment::center()) + .with(Padding::zero()), + ) + .with(Width::truncate(5)) + .with(MinWidth::new(5)) + .to_string(); + + assert_eq!( + table, + static_table!("+---+" "|Hel|" "+---+" "|usi|" "+---+") + ); + assert!(is_lines_equal(&table, 5)); + + let table = Matrix::table(1, 2) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(20)) + .with(MinWidth::new(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello World |" + "|--|-------|-------|" + "| | colum | colum |" + "| | 0-0 | 0-1 |" + ) + ); + assert!(is_lines_equal(&table, 20)); + + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(20)) + .with(MinWidth::new(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello World |" + "|--|----|----|-----|" + "| | co | co | col |" + "| | 0- | 0- | 0-2 |" + "| | 1- | 1- | 1-2 |" + "| | 2- | 2- | 2-2 |" + ) + ); + assert!(is_lines_equal(&table, 20)); + + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(6)) + .with(MinWidth::new(6)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello Wor |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); + assert!(is_lines_equal(&table, 13)); + + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(14)) + .with(MinWidth::new(14)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello Worl |" + "|--|--|--|---|" + "| | | | c |" + "| | | | 0 |" + "| | | | 1 |" + "| | | | 2 |" + ) + ); + assert!(is_lines_equal(&table, 14)); + + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World 123")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::truncate(14)) + .with(MinWidth::new(14)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello Worl |" + "|--|--|--|---|" + "| | | | c |" + "| | | | 0 |" + "| | | | 1 |" + "| | | | 2 |" + ) + ); + assert!(is_lines_equal(&table, 14)); +} + +#[cfg(feature = "color")] +#[test] +fn total_width_wrapping() { + let table = Matrix::new(3, 3) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::wrap(20)) + .with(MinWidth::new(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | co | co | col |" + "| | lu | lu | umn |" + "| | mn | mn | 2 |" + "| | 0 | 1 | |" + "|--|----|----|-----|" + "| | 0- | 0- | 0-2 |" + "| | 0 | 1 | |" + "| | 1- | 1- | 1-2 |" + "| | 0 | 1 | |" + "| | 2- | 2- | 2-2 |" + "| | 0 | 1 | |" + ) + ); + assert!(is_lines_equal(&table, 20)); + + let table = Matrix::new(3, 3) + .insert((3, 2), "some loong string") + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::wrap(20).keep_words()) + .with(MinWidth::new(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | | column | |" + "| | | 1 | |" + "|--|--|---------|--|" + "| | | 0-1 | |" + "| | | 1-1 | |" + "| | | some | |" + "| | | loong | |" + "| | | string | |" + ) + ); + assert!(is_lines_equal(&table, 20)); +} + +#[test] +fn total_width_small_with_panel_using_wrapping() { + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::wrap(20)) + .with(MinWidth::new(20)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello World |" + "|--|----|----|-----|" + "| | co | co | col |" + "| | lu | lu | umn |" + "| | mn | mn | 2 |" + "| | 0 | 1 | |" + "| | 0- | 0- | 0-2 |" + "| | 0 | 1 | |" + "| | 1- | 1- | 1-2 |" + "| | 0 | 1 | |" + "| | 2- | 2- | 2-2 |" + "| | 0 | 1 | |" + ) + ); + assert!(is_lines_equal(&table, 20)); + + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::wrap(14)) + .with(MinWidth::new(14)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello Worl |" + "| d |" + "|--|--|--|---|" + "| | | | c |" + "| | | | o |" + "| | | | l |" + "| | | | u |" + "| | | | m |" + "| | | | n |" + "| | | | |" + "| | | | 2 |" + "| | | | 0 |" + "| | | | - |" + "| | | | 2 |" + "| | | | 1 |" + "| | | | - |" + "| | | | 2 |" + "| | | | 2 |" + "| | | | - |" + "| | | | 2 |" + ) + ); + assert!(is_lines_equal(&table, 14)); + + let table = Matrix::new(3, 3) + .with(Panel::horizontal(0, "Hello World 123")) + .with(Modify::new(Segment::all()).with(Alignment::center())) + .with(Style::markdown()) + .with(Width::wrap(14)) + .with(MinWidth::new(14)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| Hello Worl |" + "| d 123 |" + "|--|--|--|---|" + "| | | | c |" + "| | | | o |" + "| | | | l |" + "| | | | u |" + "| | | | m |" + "| | | | n |" + "| | | | |" + "| | | | 2 |" + "| | | | 0 |" + "| | | | - |" + "| | | | 2 |" + "| | | | 1 |" + "| | | | - |" + "| | | | 2 |" + "| | | | 2 |" + "| | | | - |" + "| | | | 2 |" + ) + ); + assert!(is_lines_equal(&table, 14)); +} + +#[test] +fn max_width_with_span() { + let mut table = Matrix::new(3, 3).insert((1, 1), "a long string").to_table(); + table + .with(Style::psql()) + .with(Modify::new((1, 1)).with(Span::column(2))) + .with(Modify::new((2, 2)).with(Span::column(2))); + + table.with(Width::truncate(40)); + + assert_eq!( + table.to_string(), + static_table!( + " N | column 0 | column 1 | column 2 " + "---+----------+----------+----------" + " 0 | a long string | 0-2 " + " 1 | 1-0 | 1-1 " + " 2 | 2-0 | 2-1 | 2-2 " + ) + ); + assert!(is_lines_equal(&table.to_string(), 36)); + + table.with(Width::truncate(20)); + + assert_eq!( + table.to_string(), + static_table!( + " | col | col | col " + "--+-----+-----+-----" + " | a long st | 0-2 " + " | 1-0 | 1-1 " + " | 2-0 | 2-1 | 2-2 " + ) + ); + assert!(is_lines_equal(&table.to_string(), 20)); + + table.with(Width::truncate(10)); + + assert_eq!( + table.to_string(), + static_table!( + " | | | " + "--+--+--+--" + " | a l | " + " | | 1-1 " + " | | | " + ) + ); + assert!(is_lines_equal(&table.to_string(), 11)); +} + +#[test] +fn min_width_works_with_right_alignment() { + let json = r#" + { + "some": "random", + "json": [ + { "1": "2" }, + { "1": "2" }, + { "1": "2" } + ] + } + "#; + + let mut table = Matrix::iter([json]); + table + .with(Style::markdown()) + .with( + Modify::new(Segment::all()) + .with(Alignment::right()) + .with(TrimStrategy::None), + ) + .with(MinWidth::new(50)); + + assert_eq!(string_width_multiline(&table.to_string()), 50); + assert_eq!( + table.to_string(), + static_table!( + "| &str |" + "|------------------------------------------------|" + "| |" + "| { |" + "| \"some\": \"random\", |" + "| \"json\": [ |" + "| { \"1\": \"2\" }, |" + "| { \"1\": \"2\" }, |" + "| { \"1\": \"2\" } |" + "| ] |" + "| } |" + "| |" + ) + ); + + table + .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)) + .with(MinWidth::new(50)); + + assert_eq!( + table.to_string(), + static_table!( + r#"| &str |"# + r#"|------------------------------------------------|"# + r#"| |"# + r#"| { |"# + r#"| "some": "random", |"# + r#"| "json": [ |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" } |"# + r#"| ] |"# + r#"| } |"# + r#"| |"# + ) + ); + assert!(is_lines_equal(&table.to_string(), 50)); + + table + .with(Modify::new(Segment::all()).with(TrimStrategy::Both)) + .with(MinWidth::new(50)); + + assert_eq!( + table.to_string(), + static_table!( + r#"| &str |"# + r#"|------------------------------------------------|"# + r#"| { |"# + r#"| "some": "random", |"# + r#"| "json": [ |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" } |"# + r#"| ] |"# + r#"| } |"# + r#"| |"# + r#"| |"# + ) + ); + assert!(is_lines_equal(&table.to_string(), 50)); + + let mut table = Matrix::iter([json]); + table + .with(Style::markdown()) + .with( + Modify::new(Segment::all()) + .with(Alignment::center()) + .with(TrimStrategy::None), + ) + .with(MinWidth::new(50)); + + assert_eq!( + table.to_string(), + static_table!( + "| &str |" + "|------------------------------------------------|" + "| |" + "| { |" + "| \"some\": \"random\", |" + "| \"json\": [ |" + "| { \"1\": \"2\" }, |" + "| { \"1\": \"2\" }, |" + "| { \"1\": \"2\" } |" + "| ] |" + "| } |" + "| |" + ) + ); + assert_eq!(string_width_multiline(&table.to_string()), 50); + + table + .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)) + .with(MinWidth::new(50)); + + assert_eq!( + table.to_string(), + static_table!( + r#"| &str |"# + r#"|------------------------------------------------|"# + r#"| |"# + r#"| { |"# + r#"| "some": "random", |"# + r#"| "json": [ |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" } |"# + r#"| ] |"# + r#"| } |"# + r#"| |"# + ) + ); + assert!(is_lines_equal(&table.to_string(), 50)); + + table + .with(Modify::new(Segment::all()).with(TrimStrategy::Both)) + .with(MinWidth::new(50)); + + assert_eq!( + table.to_string(), + static_table!( + r#"| &str |"# + r#"|------------------------------------------------|"# + r#"| { |"# + r#"| "some": "random", |"# + r#"| "json": [ |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" }, |"# + r#"| { "1": "2" } |"# + r#"| ] |"# + r#"| } |"# + r#"| |"# + r#"| |"# + ) + ); + assert!(is_lines_equal(&table.to_string(), 50)); +} + +#[test] +fn min_width_with_span_1() { + let data = [ + ["0", "1"], + ["a long string which will affect min width logic", ""], + ["2", "3"], + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new((1, 0)).with(Span::column(2))) + .with(MinWidth::new(100)) + .to_string(); + + assert_eq!(string_width_multiline(&table), 100); + assert_eq!( + table, + static_table!( + "| 0 | 1 |" + "|------------------------------------------------------------------------|-------------------------|" + "| 0 |" + "| a long string which will affect min width logic | |" + "| 2 | 3 |" + ) + ); + assert!(is_lines_equal(&table, 100)); +} + +#[test] +fn min_width_with_span_2() { + let data = [ + ["0", "1"], + ["a long string which will affect min width logic", ""], + ["2", "3"], + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new((2, 0)).with(Span::column(2))) + .with(MinWidth::new(100)) + .to_string(); + + assert_eq!(string_width_multiline(&table), 100); + assert_eq!( + table, + static_table!( + "| 0 | 1 |" + "|-------------------------------------------------|------------------------------------------------|" + "| 0 | 1 |" + "| a long string which will affect min width logic |" + "| 2 | 3 |" + ) + ); +} + +#[test] +fn justify_width_constant_test() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Justify::new(3)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | col | col | col |" + "|-----|-----|-----|-----|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn justify_width_constant_different_sizes_test() { + let table = Matrix::new(3, 3) + .insert((1, 1), "Hello World") + .insert((3, 2), "multi\nline string\n") + .with(Style::markdown()) + .with(Justify::new(3)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | col | col | col |" + "|-----|-----|-----|-----|" + "| 0 | Hel | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | mul | 2-2 |" + ) + ); +} + +#[test] +fn justify_width_constant_0_test() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Justify::new(0)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | | | |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); +} + +#[test] +fn justify_width_min_test() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Justify::min()) + .to_string(); + + println!("{table}"); + + assert_eq!( + table, + static_table!( + "| N | c | c | c |" + "|---|---|---|---|" + "| 0 | 0 | 0 | 0 |" + "| 1 | 1 | 1 | 1 |" + "| 2 | 2 | 2 | 2 |" + ) + ); +} + +#[test] +fn justify_width_max_test() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Justify::max()) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|----------|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); +} + +#[test] +fn max_width_when_cell_has_tabs() { + let table = Matrix::new(3, 3) + .insert((2, 1), "\tHello\tWorld\t") + .with(TabSize::new(4)) + .with(Style::markdown()) + .with(Modify::new(Columns::new(..)).with(Width::truncate(1))) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | c | c | c |" + "|---|---|---|---|" + "| 0 | 0 | 0 | 0 |" + "| 1 | | 1 | 1 |" + "| 2 | 2 | 2 | 2 |" + ) + ); +} + +#[test] +fn max_width_table_when_cell_has_tabs() { + let table = Matrix::new(3, 3) + .insert((2, 1), "\tHello\tWorld\t") + .with(TabSize::new(4)) + .with(Style::markdown()) + .with(Width::truncate(15)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | co | | |" + "|--|----|--|--|" + "| | 0- | | |" + "| | | | |" + "| | 2- | | |" + ) + ); +} + +// WE GOT [["", "column 0", "column 1 ", "column 2 "], ["", "0-0 ", "0-1 ", "0-2 "], ["", "Hello World With Big Line; Here w", "1-1", "1-2"], ["", "2-0 ", "Hello World With Big L", "2-2"]] +// [2, 10, 11, 12] +// 40 55 40 + +// BEFORE ADJ [2, 10, 11, 12] + +// WE GOT [["", "column 0", "column 1", "column 2"], ["", "0-0", "0-1", "0-2"], ["", "Hello World With Big Line; Here w", "1-1", "1-2"], ["", "2-0", "Hello World With Big L", "2-2"]] +// [2, 11, 12, 11] +// 41 55 40 + +// adj [2, 10, 10, 10] + +#[test] +fn max_width_truncate_with_big_span() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line; Here we gooooooo") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(3))) + .with(Width::truncate(40)) + .to_string(); + + assert_eq!(string_width_multiline(&table), 40); + assert_eq!( + table, + static_table!( + "| | column 0 | column 1 | column 2 |" + "|--|-----------|-----------|-----------|" + "| | 0-0 | 0-1 | 0-2 |" + "| | Hello World With Big Line; Here w |" + "| | 2-0 | 2-1 | 2-2 |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line; Here we gooooooo") + .insert((3, 2), "Hello World With Big Line; Here") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(3))) + .with(Modify::new((3, 2)).with(Span::column(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|---|-----------|----------------|----------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | Hello World With Big Line; Here we gooooooo |" + "| 2 | 2-0 | Hello World With Big Line; Here |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line; Here we gooooooo") + .insert((3, 2), "Hello World With Big Line; Here") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(3))) + .with(Modify::new((3, 2)).with(Span::column(2))) + .with(Width::truncate(40)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | colum | column 1 | column 2 |" + "|--|-------|-------------|-------------|" + "| | 0-0 | 0-1 | 0-2 |" + "| | Hello World With Big Line; Here w |" + "| | 2-0 | Hello World With Big Line |" + ) + ); + assert_eq!(string_width_multiline(&table), 40); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line; Here we gooooooo") + .insert((3, 2), "Hello World With Big Line; Here") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(2))) + .with(Modify::new((3, 2)).with(Span::column(2))) + .with(Width::truncate(40)) + .to_string(); + + assert_eq!(string_width_multiline(&table), 40); + assert_eq!( + table, + static_table!( + "| | column 0 | column 1 | c |" + "|--|---------------|---------------|---|" + "| | 0-0 | 0-1 | 0 |" + "| | Hello World With Big Line; He | 1 |" + "| | 2-0 | Hello World With |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line; Here w") + .insert((3, 2), "Hello World With Big L") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(3))) + .with(Modify::new((3, 2)).with(Span::column(2))) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|---|----------|------------|-----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | Hello World With Big Line; Here w |" + "| 2 | 2-0 | Hello World With Big L |" + ) + ); +} + +#[test] +fn max_width_truncate_priority_max() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::truncate(35).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 35)); + assert_eq!( + table, + static_table!( + "| N | column | column | column |" + "|---|---------|---------|---------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | Hello W | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::truncate(20).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 20)); + assert_eq!( + table, + static_table!( + "| N | co | co | co |" + "|---|----|----|----|" + "| 0 | 0- | 0- | 0- |" + "| 1 | He | 1- | 1- |" + "| 2 | 2- | 2- | 2- |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::truncate(0).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 13)); + assert_eq!( + table, + static_table!( + "| | | | |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); +} + +#[test] +fn max_width_truncate_priority_max_with_span() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(2))) + .with(Width::truncate(15).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 15)); + assert_eq!( + table, + static_table!( + "| N | c | | |" + "|---|---|--|--|" + "| 0 | 0 | | |" + "| 1 | Hell | |" + "| 2 | 2 | | |" + ) + ); +} + +#[test] +fn max_width_wrap_priority_max() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::wrap(35).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 35)); + assert_eq!( + table, + static_table!( + "| N | column | column | column |" + "| | 0 | 1 | 2 |" + "|---|---------|---------|---------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | Hello W | 1-1 | 1-2 |" + "| | orld Wi | | |" + "| | th Big | | |" + "| | Line | | |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::wrap(20).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 20)); + assert_eq!( + table, + static_table!( + "| N | co | co | co |" + "| | lu | lu | lu |" + "| | mn | mn | mn |" + "| | 0 | 1 | 2 |" + "|---|----|----|----|" + "| 0 | 0- | 0- | 0- |" + "| | 0 | 1 | 2 |" + "| 1 | He | 1- | 1- |" + "| | ll | 1 | 2 |" + "| | o | | |" + "| | Wo | | |" + "| | rl | | |" + "| | d | | |" + "| | Wi | | |" + "| | th | | |" + "| | B | | |" + "| | ig | | |" + "| | L | | |" + "| | in | | |" + "| | e | | |" + "| 2 | 2- | 2- | 2- |" + "| | 0 | 1 | 2 |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::wrap(0).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 13)); + assert_eq!( + table, + static_table!( + "| | | | |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); +} + +#[test] +fn max_width_wrap_priority_max_with_span() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(2))) + .with(Width::wrap(15).priority::<PriorityMax>()) + .to_string(); + + assert!(is_lines_equal(&table, 15)); + assert_eq!( + table, + static_table!( + "| N | c | | |" + "| | o | | |" + "| | l | | |" + "| | u | | |" + "| | m | | |" + "| | n | | |" + "| | | | |" + "| | 0 | | |" + "|---|---|--|--|" + "| 0 | 0 | | |" + "| | - | | |" + "| | 0 | | |" + "| 1 | Hell | |" + "| | o Wo | |" + "| | rld | |" + "| | With | |" + "| | Big | |" + "| | Lin | |" + "| | e | |" + "| 2 | 2 | | |" + "| | - | | |" + "| | 0 | | |" + ) + ); +} + +#[test] +fn max_width_truncate_priority_min() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::truncate(35).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 35)); + assert_eq!( + table, + static_table!( + "| | column 0 | | |" + "|--|------------------------|--|--|" + "| | 0-0 | | |" + "| | Hello World With Big L | | |" + "| | 2-0 | | |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::truncate(20).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 20)); + assert_eq!( + table, + static_table!( + "| | column | | |" + "|--|---------|--|--|" + "| | 0-0 | | |" + "| | Hello W | | |" + "| | 2-0 | | |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::truncate(0).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 13)); + assert_eq!( + table, + static_table!( + "| | | | |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); +} + +#[test] +fn max_width_truncate_priority_min_with_span() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(2))) + .with(Width::truncate(15).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 15)); + assert_eq!( + table, + static_table!( + "| | | co | |" + "|--|--|----|--|" + "| | | 0- | |" + "| | Hello | |" + "| | | 2- | |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(2))) + .with(Width::truncate(17).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 17)); + assert_eq!( + table, + static_table!( + "| | | colu | |" + "|--|--|------|--|" + "| | | 0-1 | |" + "| | Hello W | |" + "| | | 2-1 | |" + ) + ); +} + +#[test] +fn max_width_wrap_priority_min() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::wrap(35).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 35)); + assert_eq!( + table, + static_table!( + "| | column 0 | | |" + "|--|------------------------|--|--|" + "| | 0-0 | | |" + "| | Hello World With Big L | | |" + "| | ine | | |" + "| | 2-0 | | |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::wrap(20).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 20)); + assert_eq!( + table, + static_table!( + "| | column | | |" + "| | 0 | | |" + "|--|---------|--|--|" + "| | 0-0 | | |" + "| | Hello W | | |" + "| | orld Wi | | |" + "| | th Big | | |" + "| | Line | | |" + "| | 2-0 | | |" + ) + ); + + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Width::wrap(0).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 13)); + assert_eq!( + table, + static_table!( + "| | | | |" + "|--|--|--|--|" + "| | | | |" + "| | | | |" + "| | | | |" + ) + ); +} + +#[test] +fn max_width_wrap_priority_min_with_span() { + let table = Matrix::new(3, 3) + .insert((2, 1), "Hello World With Big Line") + .with(Style::markdown()) + .with(Modify::new((2, 1)).with(Span::column(2))) + .with(Width::wrap(15).priority::<PriorityMin>()) + .to_string(); + + assert!(is_lines_equal(&table, 15)); + assert_eq!( + table, + static_table!( + "| | | co | |" + "| | | lu | |" + "| | | mn | |" + "| | | 1 | |" + "|--|--|----|--|" + "| | | 0- | |" + "| | | 1 | |" + "| | Hello | |" + "| | Worl | |" + "| | d Wit | |" + "| | h Big | |" + "| | Line | |" + "| | | 2- | |" + "| | | 1 | |" + ) + ); +} + +#[test] +fn min_width_priority_max() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(MinWidth::new(60).priority::<PriorityMax>()) + .to_string(); + + assert_eq!(string_width_multiline(&table), 60); + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|---|----------|----------|--------------------------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ), + ); +} + +#[test] +fn min_width_priority_min() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(MinWidth::new(60).priority::<PriorityMin>()) + .to_string(); + + assert_eq!(string_width_multiline(&table), 60); + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|--------------|--------------|--------------|-------------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ), + ); +} + +#[test] +fn max_width_tab_0() { + let table = + Matrix::iter(["\t\tTigre Ecuador\tOMYA Andina\t3824909999\tCalcium carbonate\tColombia\t"]) + .with(TabSize::new(4)) + .with(Style::markdown()) + .with(Width::wrap(60)) + .to_string(); + + assert!(is_lines_equal(&table, 60)); + assert_eq!( + table, + static_table!( + "| &str |" + "|----------------------------------------------------------|" + "| Tigre Ecuador OMYA Andina 3824909999 Ca |" + "| lcium carbonate Colombia |" + ) + ); +} + +#[test] +fn min_width_is_not_used_after_padding() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(MinWidth::new(60)) + .with(Modify::new((0, 0)).with(Padding::new(2, 2, 0, 0))) + .to_string(); + + assert_eq!(string_width_multiline(&table), 40); + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|-----|----------|----------|----------|" + "| 0 | 0-0 | 0-1 | 0-2 |" + "| 1 | 1-0 | 1-1 | 1-2 |" + "| 2 | 2-0 | 2-1 | 2-2 |" + ), + ); +} + +#[test] +fn min_width_is_used_after_margin() { + let table = Matrix::new(3, 3) + .with(Style::markdown()) + .with(Margin::new(1, 1, 1, 1)) + .with(Width::increase(60)) + .to_string(); + + assert_eq!(string_width_multiline(&table), 60); + assert_eq!( + table, + static_table!( + " " + " | N | column 0 | column 1 | column 2 | " + " |--------|---------------|---------------|---------------| " + " | 0 | 0-0 | 0-1 | 0-2 | " + " | 1 | 1-0 | 1-1 | 1-2 | " + " | 2 | 2-0 | 2-1 | 2-2 | " + " " + ), + ); +} + +#[test] +fn wrap_keeping_words_0() { + let data = vec![["Hello world"]]; + let table = tabled::Table::new(data) + .with(Width::wrap(8).keep_words()) + .to_string(); + + assert_eq!( + tabled::grid::util::string::string_width_multiline(&table), + 8 + ); + + assert_eq!( + table, + static_table!( + "+------+" + "| 0 |" + "+------+" + "| Hell |" + "| o wo |" + "| rld |" + "+------+" + ) + ); +} + +#[test] +fn cell_truncate_multiline() { + let table = Matrix::new(3, 3) + .insert((1, 1), "H\nel\nlo World") + .insert((3, 2), "multi\nline string\n") + .with(Style::markdown()) + .with( + Modify::new(Columns::new(1..2).not(Rows::single(0))) + .with(Width::truncate(1).multiline()), + ) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|---|----------|-------------|----------|" + "| 0 | H | 0-1 | 0-2 |" + "| | e | | |" + "| | l | | |" + "| 1 | 1 | 1-1 | 1-2 |" + "| 2 | 2 | multi | 2-2 |" + "| | | line string | |" + "| | | | |" + ) + ); +} + +#[test] +fn cell_truncate_multiline_with_suffix() { + let table = Matrix::new(3, 3) + .insert((1, 1), "H\nel\nlo World") + .insert((3, 2), "multi\nline string\n") + .with(Style::markdown()) + .with( + Modify::new(Columns::new(1..2).not(Rows::single(0))) + .with(Width::truncate(1).multiline().suffix(".")), + ) + .to_string(); + + assert_eq!( + table, + static_table!( + "| N | column 0 | column 1 | column 2 |" + "|---|----------|-------------|----------|" + "| 0 | . | 0-1 | 0-2 |" + "| | . | | |" + "| | . | | |" + "| 1 | . | 1-1 | 1-2 |" + "| 2 | . | multi | 2-2 |" + "| | | line string | |" + "| | | | |" + ) + ); +} + +#[test] +fn table_truncate_multiline() { + let table = Matrix::new(3, 3) + .insert((1, 1), "H\nel\nlo World") + .insert((3, 2), "multi\nline string\n") + .with(Style::markdown()) + .with(Width::truncate(20).multiline()) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | c | colu | co |" + "|--|---|------|----|" + "| | H | 0-1 | 0- |" + "| | e | | |" + "| | l | | |" + "| | 1 | 1-1 | 1- |" + "| | 2 | mult | 2- |" + "| | | line | |" + "| | | | |" + ) + ); +} + +#[test] +fn table_truncate_multiline_with_suffix() { + let table = Matrix::new(3, 3) + .insert((1, 1), "H\nel\nlo World") + .insert((3, 2), "multi\nline string\n") + .with(Style::markdown()) + .with(Width::truncate(20).suffix(".").multiline()) + .to_string(); + + assert_eq!( + table, + static_table!( + "| | . | col. | c. |" + "|--|---|------|----|" + "| | . | 0-1 | 0. |" + "| | . | | |" + "| | . | | |" + "| | . | 1-1 | 1. |" + "| | . | mul. | 2. |" + "| | | lin. | |" + "| | | . | |" + ) + ); +} + +#[cfg(feature = "derive")] +mod derived { + use super::*; + + use tabled::Tabled; + + #[test] + fn wrapping_as_total_multiline() { + #[derive(Tabled)] + struct D<'a>( + #[tabled(rename = "version")] &'a str, + #[tabled(rename = "published_date")] &'a str, + #[tabled(rename = "is_active")] &'a str, + #[tabled(rename = "major_feature")] &'a str, + ); + + let data = vec![ + D("0.2.1", "2021-06-23", "true", "#[header(inline)] attribute"), + D("0.2.0", "2021-06-19", "false", "API changes"), + D("0.1.4", "2021-06-07", "false", "display_with attribute"), + ]; + + let table = Matrix::iter(&data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Width::wrap(57)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| ver | published_d | is_act | major_feature |" + "| sio | ate | ive | |" + "| n | | | |" + "|-----|-------------|--------|--------------------------|" + "| 0.2 | 2021-06-23 | true | #[header(inline)] attrib |" + "| .1 | | | ute |" + "| 0.2 | 2021-06-19 | false | API changes |" + "| .0 | | | |" + "| 0.1 | 2021-06-07 | false | display_with attribute |" + "| .4 | | | |" + ) + ); + assert!(is_lines_equal(&table, 57)); + + let table = Matrix::iter(&data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Width::wrap(57).keep_words()) + .to_string(); + + assert_eq!( + table, + static_table!( + "| ver | published_d | is_act | major_feature |" + "| sio | ate | ive | |" + "| n | | | |" + "|-----|-------------|--------|--------------------------|" + "| 0.2 | 2021-06-23 | true | #[header(inline)] |" + "| .1 | | | attribute |" + "| 0.2 | 2021-06-19 | false | API changes |" + "| .0 | | | |" + "| 0.1 | 2021-06-07 | false | display_with attribute |" + "| .4 | | | |" + ) + ); + assert!(is_lines_equal(&table, 57)); + } + + #[cfg(feature = "color")] + #[test] + fn wrapping_as_total_multiline_color() { + #[derive(Tabled)] + struct D( + #[tabled(rename = "version")] String, + #[tabled(rename = "published_date")] String, + #[tabled(rename = "is_active")] String, + #[tabled(rename = "major_feature")] String, + ); + + let data = vec![ + D( + "0.2.1".red().to_string(), + "2021-06-23".red().on_truecolor(8, 10, 30).to_string(), + "true".to_string(), + "#[header(inline)] attribute" + .blue() + .on_color(AnsiColors::Green) + .to_string(), + ), + D( + "0.2.0".red().to_string(), + "2021-06-19".green().on_truecolor(8, 100, 30).to_string(), + "false".to_string(), + "API changes".yellow().to_string(), + ), + D( + "0.1.4".white().to_string(), + "2021-06-07".red().on_truecolor(8, 10, 30).to_string(), + "false".to_string(), + "display_with attribute" + .red() + .on_color(AnsiColors::Black) + .to_string(), + ), + ]; + + let table = Matrix::iter(&data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Width::wrap(57)) + .to_string(); + + assert_eq!( + table, + static_table!( + "| ver | published_d | is_act | major_feature |" + "| sio | ate | ive | |" + "| n | | | |" + "|-----|-------------|--------|--------------------------|" + "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34m\u{1b}[42m#[header(inline)] attrib\u{1b}[39m\u{1b}[49m |" + "| \u{1b}[31m.1\u{1b}[39m | | | \u{1b}[34m\u{1b}[42mute\u{1b}[39m\u{1b}[49m |" + "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;100;30m\u{1b}[32m2021-06-19\u{1b}[39m\u{1b}[49m | false | \u{1b}[33mAPI changes\u{1b}[39m |" + "| \u{1b}[31m.0\u{1b}[39m | | | |" + "| \u{1b}[37m0.1\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31;40mdisplay_with attribute\u{1b}[0m |" + "| \u{1b}[37m.4\u{1b}[39m | | | |" + ) + ); + assert_eq!(string_width_multiline(&table), 57); + + let table = Matrix::iter(&data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Width::wrap(57).keep_words()) + .to_string(); + + assert_eq!( + table, + static_table!( + "| ver | published_d | is_act | major_feature |" + "| sio | ate | ive | |" + "| n | | | |" + "|-----|-------------|--------|--------------------------|" + "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34m\u{1b}[42m#[header(inline)] \u{1b}[39m\u{1b}[49m |" + "| \u{1b}[31m.1\u{1b}[39m | | | \u{1b}[34m\u{1b}[42mattribute\u{1b}[39m\u{1b}[49m |" + "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;100;30m\u{1b}[32m2021-06-19\u{1b}[39m\u{1b}[49m | false | \u{1b}[33mAPI changes\u{1b}[39m |" + "| \u{1b}[31m.0\u{1b}[39m | | | |" + "| \u{1b}[37m0.1\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31;40mdisplay_with attribute\u{1b}[0m |" + "| \u{1b}[37m.4\u{1b}[39m | | | |" + ) + ); + assert_eq!(string_width_multiline(&table), 57); + } + + #[cfg(feature = "color")] + #[test] + fn truncating_as_total_multiline_color() { + #[derive(Tabled)] + struct D( + #[tabled(rename = "version")] String, + #[tabled(rename = "published_date")] String, + #[tabled(rename = "is_active")] String, + #[tabled(rename = "major_feature")] String, + ); + + let data = vec![ + D( + "0.2.1".red().to_string(), + "2021-06-23".red().on_truecolor(8, 10, 30).to_string(), + "true".to_string(), + "#[header(inline)] attribute" + .blue() + .on_color(AnsiColors::Green) + .to_string(), + ), + D( + "0.2.0".red().to_string(), + "2021-06-19".green().on_truecolor(8, 100, 30).to_string(), + "false".to_string(), + "API changes".yellow().to_string(), + ), + D( + "0.1.4".white().to_string(), + "2021-06-07".red().on_truecolor(8, 10, 30).to_string(), + "false".to_string(), + "display_with attribute" + .red() + .on_color(AnsiColors::Black) + .to_string(), + ), + ]; + + let table = Matrix::iter(data) + .with(Style::markdown()) + .with(Modify::new(Segment::all()).with(Alignment::left())) + .with(Width::truncate(57)) + .to_string(); + + assert_eq!( + ansi_str::AnsiStr::ansi_strip(&table), + static_table!( + "| ver | published_d | is_act | major_feature |" + "|-----|-------------|--------|--------------------------|" + "| 0.2 | 2021-06-23 | true | #[header(inline)] attrib |" + "| 0.2 | 2021-06-19 | false | API changes |" + "| 0.1 | 2021-06-07 | false | display_with attribute |" + ) + ); + + assert_eq!( + table, + "| ver | published_d | is_act | major_feature |\n|-----|-------------|--------|--------------------------|\n| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34;42m#[header(inline)] attrib\u{1b}[39m\u{1b}[49m |\n| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;100;30m\u{1b}[32m2021-06-19\u{1b}[39m\u{1b}[49m | false | \u{1b}[33mAPI changes\u{1b}[39m |\n| \u{1b}[37m0.1\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31;40mdisplay_with attribute\u{1b}[0m |" + ); + assert_eq!(string_width_multiline(&table), 57); + } + + #[cfg(feature = "color")] + fn format_osc8_hyperlink(url: &str, text: &str) -> String { + format!( + "{osc}8;;{url}{st}{text}{osc}8;;{st}", + url = url, + text = text, + osc = "\x1b]", + st = "\x1b\\" + ) + } + + #[cfg(feature = "color")] + #[test] + fn hyperlinks() { + #[derive(Tabled)] + struct Distribution { + name: String, + is_hyperlink: bool, + } + + let table = |text: &str| { + let data = [Distribution { + name: text.to_owned(), + is_hyperlink: true, + }]; + tabled::Table::new(data) + .with( + Modify::new(Segment::all()) + .with(Width::wrap(5).keep_words()) + .with(Alignment::left()), + ) + .to_string() + }; + + let text = format_osc8_hyperlink("https://www.debian.org/", "Debian"); + assert_eq!( + table(&text), + "+-------+-------+\n\ + | name | is_hy |\n\ + | | perli |\n\ + | | nk |\n\ + +-------+-------+\n\ + | \u{1b}]8;;https://www.debian.org/\u{1b}\\Debia\u{1b}]8;;\u{1b}\\ | true |\n\ + | \u{1b}]8;;https://www.debian.org/\u{1b}\\n\u{1b}]8;;\u{1b}\\ | |\n\ + +-------+-------+" + ); + + // if there's more text than a link it will be ignored + let text = format!( + "{} :link", + format_osc8_hyperlink("https://www.debian.org/", "Debian"), + ); + assert_eq!( + table(&text), + "+-------+-------+\n\ + | name | is_hy |\n\ + | | perli |\n\ + | | nk |\n\ + +-------+-------+\n\ + | Debia | true |\n\ + | n | |\n\ + | :link | |\n\ + +-------+-------+" + ); + + let text = format!( + "asd {} 2 links in a string {}", + format_osc8_hyperlink("https://www.debian.org/", "Debian"), + format_osc8_hyperlink("https://www.wikipedia.org/", "Debian"), + ); + assert_eq!( + table(&text), + static_table!( + "+-------+-------+" + "| name | is_hy |" + "| | perli |" + "| | nk |" + "+-------+-------+" + "| asd D | true |" + "| ebian | |" + "| 2 | |" + "| links | |" + "| in a | |" + "| stri | |" + "| ng De | |" + "| bian | |" + "+-------+-------+" + ) + ); + } + + #[cfg(feature = "color")] + #[test] + fn hyperlinks_with_color() { + use owo_colors::OwoColorize; + + #[derive(Tabled)] + struct Distribution { + name: String, + is_hyperlink: bool, + } + + let table = |text: &str| { + let data = [Distribution { + name: text.to_owned(), + is_hyperlink: true, + }]; + tabled::Table::new(data) + .with( + Modify::new(Segment::all()) + .with(Width::wrap(6).keep_words()) + .with(Alignment::left()), + ) + .to_string() + }; + + let text = format_osc8_hyperlink( + "https://www.debian.org/", + "Debian".red().to_string().as_str(), + ); + assert_eq!( + table(&text), + static_table!( + "+--------+--------+" + "| name | is_hyp |" + "| | erlink |" + "+--------+--------+" + "| \u{1b}]8;;https://www.debian.org/\u{1b}\\\u{1b}[31mDebian\u{1b}[39m\u{1b}]8;;\u{1b}\\ | true |" + "+--------+--------+" + ) + ); + + // if there's more text than a link it will be ignored + let text = format!( + "{} :link", + format_osc8_hyperlink("https://www.debian.org/", "Debian"), + ); + assert_eq!( + table(&text), + static_table!( + "+--------+--------+" + "| name | is_hyp |" + "| | erlink |" + "+--------+--------+" + "| Debian | true |" + "| :link | |" + "+--------+--------+" + ) + ); + + let text = format!( + "asd {} 2 links in a string {}", + format_osc8_hyperlink("https://www.debian.org/", "Debian"), + format_osc8_hyperlink("https://www.wikipedia.org/", "Debian"), + ); + assert_eq!( + table(&text), + static_table!( + "+--------+--------+" + "| name | is_hyp |" + "| | erlink |" + "+--------+--------+" + "| asd | true |" + "| Debian | |" + "| 2 | |" + "| links | |" + "| in a | |" + "| string | |" + "| | |" + "| Debian | |" + "+--------+--------+" + ) + ); + } +} |