@@ -711,3 +711,120 @@ def args_in_kwargs(args: Sequence[str], kwargs: dict[str, Any]) -> bool:
711711 return any (
712712 kwargs .get (arg ) is not None and kwargs .get (arg ) is not False for arg in args
713713 )
714+
715+
716+ def sequence_join (
717+ value : Any ,
718+ separator : str = "/" ,
719+ size : int | Sequence [int ] | None = None ,
720+ ndim : int = 1 ,
721+ name : str | None = None ,
722+ ) -> str | list [str ] | None | Any :
723+ """
724+ Join a sequence of values into a string separated by a separator.
725+
726+ A 1-D sequence will be joined into a single string. A 2-D sequence will be joined
727+ into a list of strings. Non-sequence values will be returned as is.
728+
729+ Parameters
730+ ----------
731+ value
732+ The 1-D or 2-D sequence of values to join.
733+ separator
734+ The separator to join the values.
735+ size
736+ Expected size of the 1-D sequence. It can be either an integer or a sequence of
737+ integers. If an integer, it is the expected size of the 1-D sequence. If it is a
738+ sequence, it is the allowed sizes of the 1-D sequence.
739+ ndim
740+ The expected maximum number of dimensions of the sequence.
741+ name
742+ The name of the parameter to be used in the error message.
743+
744+ Returns
745+ -------
746+ joined_value
747+ The joined string or list of strings.
748+
749+ Examples
750+ --------
751+ >>> sequence_join("1/2/3/4")
752+ '1/2/3/4'
753+ >>> sequence_join(None)
754+ >>> sequence_join(True)
755+ True
756+ >>> sequence_join(False)
757+ False
758+
759+ >>> sequence_join([])
760+ Traceback (most recent call last):
761+ ...
762+ pygmt.exceptions.GMTInvalidInput: Expected a sequence but got an empty sequence.
763+
764+ >>> sequence_join([1, 2, 3, 4])
765+ '1/2/3/4'
766+ >>> sequence_join([1, 2, 3, 4], separator=",")
767+ '1,2,3,4'
768+ >>> sequence_join([1, 2, 3, 4], separator="/", size=4)
769+ '1/2/3/4'
770+ >>> sequence_join([1, 2, 3, 4], separator="/", size=[2, 4])
771+ '1/2/3/4'
772+ >>> sequence_join([1, 2, 3, 4], separator="/", size=[2, 4], ndim=2)
773+ '1/2/3/4'
774+ >>> sequence_join([1, 2, 3, 4], separator="/", size=2)
775+ Traceback (most recent call last):
776+ ...
777+ pygmt.exceptions.GMTInvalidInput: Expected a sequence of 2 values, but got 4 values.
778+ >>> sequence_join([1, 2, 3, 4, 5], separator="/", size=[2, 4], name="parname")
779+ Traceback (most recent call last):
780+ ...
781+ pygmt.exceptions.GMTInvalidInput: Parameter 'parname': Expected ...
782+
783+ >>> sequence_join([[1, 2], [3, 4]], separator="/")
784+ Traceback (most recent call last):
785+ ...
786+ pygmt.exceptions.GMTInvalidInput: Expected a 1-D ..., but a 2-D sequence is given.
787+ >>> sequence_join([[1, 2], [3, 4]], separator="/", ndim=2)
788+ ['1/2', '3/4']
789+ >>> sequence_join([[1, 2], [3, 4]], separator="/", size=2, ndim=2)
790+ ['1/2', '3/4']
791+ >>> sequence_join([[1, 2], [3, 4]], separator="/", size=4, ndim=2)
792+ Traceback (most recent call last):
793+ ...
794+ pygmt.exceptions.GMTInvalidInput: Expected a sequence of 4 values.
795+ >>> sequence_join([[1, 2], [3, 4]], separator="/", size=[2, 4], ndim=2)
796+ ['1/2', '3/4']
797+ """
798+ # Return the original value if it is not a sequence (e.g., None, bool, or str).
799+ if not is_nonstr_iter (value ):
800+ return value
801+ # Now it must be a sequence.
802+
803+ # Change size to a list to simplify the checks.
804+ size = [size ] if isinstance (size , int ) else size
805+ errmsg = {
806+ "name" : f"Parameter '{ name } ': " if name else "" ,
807+ "sizes" : ", " .join (str (s ) for s in size ) if size is not None else "" ,
808+ }
809+
810+ if len (value ) == 0 :
811+ msg = f"{ errmsg ['name' ]} Expected a sequence but got an empty sequence."
812+ raise GMTInvalidInput (msg )
813+
814+ if not is_nonstr_iter (value [0 ]): # 1-D sequence.
815+ if size is not None and len (value ) not in size :
816+ msg = (
817+ f"{ errmsg ['name' ]} Expected a sequence of { errmsg ['sizes' ]} values, "
818+ f"but got { len (value )} values."
819+ )
820+ raise GMTInvalidInput (msg )
821+ return separator .join (str (v ) for v in value )
822+
823+ # Now it must be a 2-D sequence.
824+ if ndim == 1 :
825+ msg = f"{ errmsg ['name' ]} Expected a 1-D sequence, but a 2-D sequence is given."
826+ raise GMTInvalidInput (msg )
827+ if size is not None and any (len (i ) not in size for i in value ):
828+ msg = f"{ errmsg ['name' ]} Expected a sequence of { errmsg ['sizes' ]} values."
829+ raise GMTInvalidInput (msg )
830+ return [separator .join (str (j ) for j in sub ) for sub in value ]
0 commit comments