2022-03-08 15:00:27 +00:00
/*
2023-06-17 14:13:31 +01:00
* Copyright ( c ) 2022 - 2023 , Sam Atkins < atkinssj @ serenityos . org >
2022-03-08 15:00:27 +00:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "GeneratorUtil.h"
# include <AK/SourceGenerator.h>
# include <AK/StringBuilder.h>
2022-04-01 17:19:09 +01:00
# include <LibCore/ArgsParser.h>
2022-03-08 15:00:27 +00:00
# include <LibMain/Main.h>
2023-02-09 03:02:46 +01:00
ErrorOr < void > generate_header_file ( JsonObject & media_feature_data , Core : : File & file ) ;
ErrorOr < void > generate_implementation_file ( JsonObject & media_feature_data , Core : : File & file ) ;
2022-04-01 17:19:09 +01:00
2025-07-08 12:14:08 +02:00
ErrorOr < int > ladybird_main ( Main : : Arguments arguments )
2022-03-08 15:00:27 +00:00
{
2022-04-01 17:19:09 +01:00
StringView generated_header_path ;
StringView generated_implementation_path ;
StringView media_features_json_path ;
Core : : ArgsParser args_parser ;
args_parser . add_option ( generated_header_path , " Path to the MediaFeatureID header file to generate " , " generated-header-path " , ' h ' , " generated-header-path " ) ;
args_parser . add_option ( generated_implementation_path , " Path to the MediaFeatureID implementation file to generate " , " generated-implementation-path " , ' c ' , " generated-implementation-path " ) ;
args_parser . add_option ( media_features_json_path , " Path to the JSON file to read from " , " json-path " , ' j ' , " json-path " ) ;
args_parser . parse ( arguments ) ;
2022-03-08 15:00:27 +00:00
2022-04-01 17:19:09 +01:00
auto json = TRY ( read_entire_file_as_json ( media_features_json_path ) ) ;
2022-03-08 15:00:27 +00:00
VERIFY ( json . is_object ( ) ) ;
2022-04-01 17:19:09 +01:00
auto media_feature_data = json . as_object ( ) ;
2023-02-09 03:02:46 +01:00
auto generated_header_file = TRY ( Core : : File : : open ( generated_header_path , Core : : File : : OpenMode : : Write ) ) ;
auto generated_implementation_file = TRY ( Core : : File : : open ( generated_implementation_path , Core : : File : : OpenMode : : Write ) ) ;
2022-04-01 17:19:09 +01:00
TRY ( generate_header_file ( media_feature_data , * generated_header_file ) ) ;
TRY ( generate_implementation_file ( media_feature_data , * generated_implementation_file ) ) ;
return 0 ;
}
2023-02-09 03:02:46 +01:00
ErrorOr < void > generate_header_file ( JsonObject & media_feature_data , Core : : File & file )
2022-04-01 17:19:09 +01:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
2025-05-19 12:32:37 +01:00
generator . set ( " media_feature_id_underlying_type " , underlying_type_for_enum ( media_feature_data . size ( ) ) ) ;
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(#pragma once
2022-04-01 17:19:09 +01:00
# include <AK/StringView.h>
# include <AK/Traits.h>
2024-08-14 14:06:03 +01:00
# include <LibWeb/CSS/Keyword.h>
2022-04-01 17:19:09 +01:00
namespace Web : : CSS {
enum class MediaFeatureValueType {
Boolean ,
Integer ,
Length ,
Ratio ,
Resolution ,
} ;
2025-05-19 12:32:37 +01:00
enum class MediaFeatureID : @ media_feature_id_underlying_type @ { ) ~ ~ ~ " );
2022-04-01 17:19:09 +01:00
2023-08-22 09:09:07 +02:00
media_feature_data . for_each_member ( [ & ] ( auto & name , auto & ) {
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
@ name : titlecase @ , ) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2022-04-01 17:19:09 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-04-01 17:19:09 +01:00
} ;
Optional < MediaFeatureID > media_feature_id_from_string ( StringView ) ;
2023-01-06 19:02:26 +01:00
StringView string_from_media_feature_id ( MediaFeatureID ) ;
2022-04-01 17:19:09 +01:00
bool media_feature_type_is_range ( MediaFeatureID ) ;
bool media_feature_accepts_type ( MediaFeatureID , MediaFeatureValueType ) ;
2024-08-14 14:06:03 +01:00
bool media_feature_accepts_keyword ( MediaFeatureID , Keyword ) ;
2022-04-01 17:19:09 +01:00
2025-05-22 17:02:44 +01:00
bool media_feature_keyword_is_falsey ( MediaFeatureID , Keyword ) ;
2022-04-01 17:19:09 +01:00
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-04-01 17:19:09 +01:00
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-04-01 17:19:09 +01:00
return { } ;
}
2022-03-08 15:00:27 +00:00
2023-02-09 03:02:46 +01:00
ErrorOr < void > generate_implementation_file ( JsonObject & media_feature_data , Core : : File & file )
2022-04-01 17:19:09 +01:00
{
2022-03-08 15:00:27 +00:00
StringBuilder builder ;
SourceGenerator generator { builder } ;
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-02-17 16:43:28 +00:00
# include <LibWeb/CSS/MediaFeatureID.h>
# include <LibWeb/Infra/Strings.h>
2022-03-08 15:00:27 +00:00
namespace Web : : CSS {
Optional < MediaFeatureID > media_feature_id_from_string ( StringView string )
2023-08-21 16:39:43 +02:00
{ ) ~ ~ ~ " );
2022-03-08 15:00:27 +00:00
2023-08-22 09:09:07 +02:00
media_feature_data . for_each_member ( [ & ] ( auto & name , auto & ) {
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-22 09:09:07 +02:00
member_generator . set ( " name " , name ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2025-05-18 15:04:56 +12:00
if ( string . equals_ignoring_ascii_case ( " @name@ " sv ) )
2022-03-08 15:00:27 +00:00
return MediaFeatureID : : @ name : titlecase @ ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-08 15:00:27 +00:00
generator . append ( R " ~~~(
return { } ;
}
2023-01-06 19:02:26 +01:00
StringView string_from_media_feature_id ( MediaFeatureID media_feature_id )
2022-03-08 15:00:27 +00:00
{
switch ( media_feature_id ) { ) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
media_feature_data . for_each_member ( [ & ] ( auto & name , auto & ) {
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-22 09:09:07 +02:00
member_generator . set ( " name " , name ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 15:00:27 +00:00
case MediaFeatureID : : @ name : titlecase @ :
2023-08-21 16:39:43 +02:00
return " @name@ " sv ; ) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-08 15:00:27 +00:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-03-08 15:00:27 +00:00
}
VERIFY_NOT_REACHED ( ) ;
}
2022-03-08 17:39:08 +00:00
bool media_feature_type_is_range ( MediaFeatureID media_feature_id )
{
2023-08-21 16:39:43 +02:00
switch ( media_feature_id ) { ) ~ ~ ~ " );
2022-03-08 17:39:08 +00:00
2023-08-22 09:09:07 +02:00
media_feature_data . for_each_member ( [ & ] ( auto & name , auto & value ) {
2022-03-08 17:39:08 +00:00
VERIFY ( value . is_object ( ) ) ;
auto & feature = value . as_object ( ) ;
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2022-07-11 17:32:29 +00:00
VERIFY ( feature . has ( " type " sv ) ) ;
2025-02-17 15:04:56 -05:00
auto feature_type = feature . get_string ( " type " sv ) ;
2022-12-21 14:37:27 +00:00
VERIFY ( feature_type . has_value ( ) ) ;
2025-02-17 15:04:56 -05:00
member_generator . set ( " is_range " , feature_type . value ( ) = = " range " sv ? " true " _string : " false " _string ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 17:39:08 +00:00
case MediaFeatureID : : @ name : titlecase @ :
2023-08-21 16:39:43 +02:00
return @ is_range @ ; ) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-08 17:39:08 +00:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-03-08 17:39:08 +00:00
}
VERIFY_NOT_REACHED ( ) ;
}
2022-03-08 20:42:57 +00:00
bool media_feature_accepts_type ( MediaFeatureID media_feature_id , MediaFeatureValueType value_type )
{
2023-08-21 16:39:43 +02:00
switch ( media_feature_id ) { ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
2023-08-22 09:09:07 +02:00
media_feature_data . for_each_member ( [ & ] ( auto & name , auto & member ) {
2022-03-08 20:42:57 +00:00
VERIFY ( member . is_object ( ) ) ;
auto & feature = member . as_object ( ) ;
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
case MediaFeatureID : : @ name : titlecase @ : ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
bool have_output_value_type_switch = false ;
2022-07-11 17:32:29 +00:00
if ( feature . has ( " values " sv ) ) {
2023-08-22 09:09:07 +02:00
auto append_value_type_switch_if_needed = [ & ] {
2022-03-08 20:42:57 +00:00
if ( ! have_output_value_type_switch ) {
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
switch ( value_type ) { ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
}
have_output_value_type_switch = true ;
} ;
2022-12-21 14:37:27 +00:00
auto values = feature . get_array ( " values " sv ) ;
VERIFY ( values . has_value ( ) ) ;
auto & values_array = values . value ( ) ;
2022-03-08 20:42:57 +00:00
for ( auto & type : values_array . values ( ) ) {
VERIFY ( type . is_string ( ) ) ;
auto type_name = type . as_string ( ) ;
2024-08-14 14:06:03 +01:00
// Skip keywords.
2025-02-17 13:21:07 -05:00
if ( ! type_name . starts_with ( ' < ' ) )
2022-03-08 20:42:57 +00:00
continue ;
if ( type_name = = " <mq-boolean> " ) {
2023-08-22 09:09:07 +02:00
append_value_type_switch_if_needed ( ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
case MediaFeatureValueType : : Boolean :
2023-08-21 16:39:43 +02:00
return true ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else if ( type_name = = " <integer> " ) {
2023-08-22 09:09:07 +02:00
append_value_type_switch_if_needed ( ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
case MediaFeatureValueType : : Integer :
2023-08-21 16:39:43 +02:00
return true ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else if ( type_name = = " <length> " ) {
2023-08-22 09:09:07 +02:00
append_value_type_switch_if_needed ( ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
case MediaFeatureValueType : : Length :
2023-08-21 16:39:43 +02:00
return true ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else if ( type_name = = " <ratio> " ) {
2023-08-22 09:09:07 +02:00
append_value_type_switch_if_needed ( ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
case MediaFeatureValueType : : Ratio :
2023-08-21 16:39:43 +02:00
return true ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else if ( type_name = = " <resolution> " ) {
2023-08-22 09:09:07 +02:00
append_value_type_switch_if_needed ( ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
case MediaFeatureValueType : : Resolution :
2023-08-21 16:39:43 +02:00
return true ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else {
warnln ( " Unrecognized media-feature value type: `{}` " , type_name ) ;
VERIFY_NOT_REACHED ( ) ;
}
}
}
if ( have_output_value_type_switch ) {
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
default :
return false ;
2023-08-21 16:39:43 +02:00
} ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else {
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
return false ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-08 20:42:57 +00:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
}
VERIFY_NOT_REACHED ( ) ;
}
2024-08-14 14:06:03 +01:00
bool media_feature_accepts_keyword ( MediaFeatureID media_feature_id , Keyword keyword )
2022-03-08 20:42:57 +00:00
{
2023-08-21 16:39:43 +02:00
switch ( media_feature_id ) { ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
2023-08-22 09:09:07 +02:00
media_feature_data . for_each_member ( [ & ] ( auto & name , auto & member ) {
2022-03-08 20:42:57 +00:00
VERIFY ( member . is_object ( ) ) ;
auto & feature = member . as_object ( ) ;
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
case MediaFeatureID : : @ name : titlecase @ : ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
2024-08-14 14:06:03 +01:00
bool have_output_keyword_switch = false ;
2022-07-11 17:32:29 +00:00
if ( feature . has ( " values " sv ) ) {
2024-08-14 14:06:03 +01:00
auto append_keyword_switch_if_needed = [ & ] {
if ( ! have_output_keyword_switch ) {
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2024-08-14 14:06:03 +01:00
switch ( keyword ) { ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
}
2024-08-14 14:06:03 +01:00
have_output_keyword_switch = true ;
2022-03-08 20:42:57 +00:00
} ;
2022-12-21 14:37:27 +00:00
auto values = feature . get_array ( " values " sv ) ;
VERIFY ( values . has_value ( ) ) ;
auto & values_array = values . value ( ) ;
2024-08-14 14:06:03 +01:00
for ( auto & keyword : values_array . values ( ) ) {
VERIFY ( keyword . is_string ( ) ) ;
2025-02-17 13:21:07 -05:00
auto const & keyword_name = keyword . as_string ( ) ;
2022-03-08 20:42:57 +00:00
// Skip types.
2025-02-17 13:21:07 -05:00
if ( keyword_name . starts_with ( ' < ' ) )
2022-03-08 20:42:57 +00:00
continue ;
2024-08-14 14:06:03 +01:00
append_keyword_switch_if_needed ( ) ;
2022-03-08 20:42:57 +00:00
2024-08-14 14:06:03 +01:00
auto keyword_generator = member_generator . fork ( ) ;
keyword_generator . set ( " keyword:titlecase " , title_casify ( keyword_name ) ) ;
keyword_generator . append ( R " ~~~(
case Keyword : : @ keyword : titlecase @ :
2023-08-21 16:39:43 +02:00
return true ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
}
}
2024-08-14 14:06:03 +01:00
if ( have_output_keyword_switch ) {
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
default :
return false ;
2023-08-21 16:39:43 +02:00
} ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
} else {
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
return false ; ) ~ ~ ~ " );
2022-03-08 20:42:57 +00:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-08 20:42:57 +00:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-03-08 20:42:57 +00:00
}
VERIFY_NOT_REACHED ( ) ;
}
2025-05-22 17:02:44 +01:00
bool media_feature_keyword_is_falsey ( MediaFeatureID media_feature_id , Keyword keyword )
{
switch ( media_feature_id ) { ) ~ ~ ~ " );
media_feature_data . for_each_member ( [ & ] ( auto & name , JsonValue const & feature_value ) {
VERIFY ( feature_value . is_object ( ) ) ;
auto & feature = feature_value . as_object ( ) ;
auto false_keywords = feature . get_array ( " false-keywords " sv ) ;
if ( ! false_keywords . has_value ( ) | | false_keywords - > is_empty ( ) )
return ;
auto member_generator = generator . fork ( ) ;
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
member_generator . append ( R " ~~~(
case MediaFeatureID : : @ name : titlecase @ :
switch ( keyword ) { ) ~ ~ ~ " );
false_keywords . value ( ) . for_each ( [ & ] ( JsonValue const & value ) {
auto value_generator = member_generator . fork ( ) ;
member_generator . set ( " false_keyword:titlecase " , title_casify ( value . as_string ( ) ) ) ;
member_generator . append ( R " ~~~(
case Keyword : : @ false_keyword : titlecase @ : ) ~ ~ ~ " );
} ) ;
member_generator . append ( R " ~~~(
return true ;
default :
return false ;
} ) ~ ~ ~ " );
} ) ;
generator . append ( R " ~~~(
default :
return false ;
}
}
2022-03-08 15:00:27 +00:00
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-03-08 15:00:27 +00:00
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-04-01 17:19:09 +01:00
return { } ;
2022-03-08 15:00:27 +00:00
}